diff --git a/.github (2).zip b/.github (2).zip new file mode 100644 index 00000000..4c99772b Binary files /dev/null and b/.github (2).zip differ diff --git a/.github.zip b/.github.zip deleted file mode 100644 index effd318b..00000000 Binary files a/.github.zip and /dev/null differ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 972a4762..4431582a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: POSTGRES_USER: investmoz_test POSTGRES_PASSWORD: test-only-password ports: - - 5432:5432 + - 55441:5432 options: >- --health-cmd "pg_isready -U investmoz_test -d investmoz_test" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -28,7 +28,7 @@ jobs: APP_ENV: testing DB_CONNECTION: pgsql DB_HOST: 127.0.0.1 - DB_PORT: 5432 + DB_PORT: 55441 DB_DATABASE: investmoz_test DB_USERNAME: investmoz_test DB_PASSWORD: test-only-password @@ -71,5 +71,5 @@ jobs: INVESTMOZ_SCHEMA_TESTS: '1' run: php vendor/bin/phpunit --configuration=phpunit.investmoz.xml --do-not-cache-result - - name: Test frontend API contracts - run: node --test tests/frontend/portal-api.test.mjs + - name: Test all frontend contracts and calculators + run: node --test tests/frontend/*.test.mjs diff --git a/api/openapi.yaml b/api/openapi.yaml index e4e85bdf..0bda09ed 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1,864 +1,1112 @@ openapi: 3.1.0 info: - title: InvestMoz API - version: 1.0.0 - summary: Contrato consolidado da API do InvestMoz - description: | - Contrato consolidado para catálogos, oportunidades, manifestações de interesse, - simulações, legislação, jobs, relatórios e administração. Valores de taxas, - reduções e parâmetros de simulação são fornecidos pelo utilizador; não são - taxas oficiais nem decisões de elegibilidade. - contact: - name: Equipa InvestMoz - license: - name: MIT - identifier: MIT + title: InvestMoz API + version: 1.0.0 + summary: Contrato consolidado da API do InvestMoz + description: | + Contrato consolidado para catálogos, oportunidades, manifestações de interesse, + simulações, legislação, jobs, relatórios e administração. Valores de taxas, + reduções e parâmetros de simulação são fornecidos pelo utilizador; não são + taxas oficiais nem decisões de elegibilidade. + contact: + name: Equipa InvestMoz + license: + name: MIT + identifier: MIT servers: - - url: / - description: Mesmo host da aplicação + - url: / + description: Mesmo host da aplicação security: - - sessionCookie: [] + - sessionCookie: [] tags: - - name: Catálogos - description: Referências versionadas, estáveis e localizadas em pt-MZ. - - name: Oportunidades - description: Consulta pública de oportunidades de investimento. - - name: Manifestações - description: Submissão de interesse em oportunidades. - - name: Simulações - description: Execuções indicativas com snapshots e controlo de concorrência. - - name: Jobs - description: Estado dos trabalhos assíncronos associados a uma simulação. - - name: Relatórios - description: Leitura de snapshots imutáveis de relatórios. - - name: Legislação - description: Fontes legais publicadas e versionadas. - - name: Administração - description: Operações administrativas protegidas por sessão e autorização. + - name: Catálogos + description: Referências versionadas, estáveis e localizadas em pt-MZ. + - name: Oportunidades + description: Consulta pública de oportunidades de investimento. + - name: Manifestações + description: Submissão de interesse em oportunidades. + - name: Simulações + description: Execuções indicativas com snapshots e controlo de concorrência. + - name: Jobs + description: Estado dos trabalhos assíncronos associados a uma simulação. + - name: Relatórios + description: Leitura de snapshots imutáveis de relatórios. + - name: Legislação + description: Fontes legais publicadas e versionadas. + - name: Administração + description: Operações administrativas protegidas por sessão e autorização. paths: - /api/v1/catalogs/{catalog}: - get: - tags: [Catálogos] - operationId: listReferenceCatalog - summary: Consultar catálogo versionado - security: [] - parameters: - - $ref: '#/components/parameters/CatalogName' - - $ref: '#/components/parameters/Version' - - $ref: '#/components/parameters/CatalogType' - - $ref: '#/components/parameters/ParentId' - - $ref: '#/components/parameters/JurisdictionId' - - $ref: '#/components/parameters/Page' - - $ref: '#/components/parameters/PerPage' - - $ref: '#/components/parameters/Search' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Página do catálogo na versão solicitada - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/CatalogPage'} - '404': {$ref: '#/components/responses/NotFound'} - '422': {$ref: '#/components/responses/ValidationError'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/questionnaires/{code}: - get: - tags: [Simulações] - operationId: getPublishedQuestionnaire - summary: Consultar questionário publicado e versão imutável - security: [] - parameters: - - $ref: '#/components/parameters/QuestionnaireCode' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Questionário publicado em pt-MZ - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/QuestionnaireResponse'} - '404': {$ref: '#/components/responses/NotFound'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/simulations: - post: - tags: [Simulações] - operationId: createSimulation - summary: Criar simulação rastreável - description: Requer Idempotency-Key. Acesso anónimo requer token opaco gerado pelo cliente. - x-implementation-status: implemented - security: - - sessionCookie: [] - - {} - parameters: - - $ref: '#/components/parameters/IdempotencyKey' - - $ref: '#/components/parameters/SimulationToken' - - $ref: '#/components/parameters/CorrelationId' - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/SimulationInput'} - examples: - BoundaryValues: - summary: Exemplo técnico nos limites mínimos - description: Valores mínimos para demonstrar a forma do payload; não são valores fiscais recomendados. - value: {base: 0, rate: 0, reduction: 0} - responses: - '202': - description: Simulação aceite para processamento - headers: - ETag: {$ref: '#/components/headers/ETag'} - Location: {$ref: '#/components/headers/Location'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/SimulationAccepted'} - '200': - description: Repetição idempotente da resposta original - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/SimulationAccepted'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '409': {$ref: '#/components/responses/Conflict'} - '422': {$ref: '#/components/responses/ValidationError'} - '428': {$ref: '#/components/responses/PreconditionRequired'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/simulations/{id}: - get: - tags: [Simulações] - operationId: getSimulation - summary: Consultar simulação, cenários e estado de execução - x-implementation-status: implemented - security: - - sessionCookie: [] - - {} - parameters: - - $ref: '#/components/parameters/SimulationId' - - $ref: '#/components/parameters/SimulationToken' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Simulação dentro do âmbito autorizado - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/SimulationResponse'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '404': {$ref: '#/components/responses/NotFound'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/simulations/{id}/scenarios/{scenarioId}: - patch: - tags: [Simulações] - operationId: updateSimulationScenario - summary: Actualizar cenário com controlo optimista de concorrência - x-implementation-status: implemented - security: - - sessionCookie: [] - - {} - parameters: - - $ref: '#/components/parameters/SimulationId' - - $ref: '#/components/parameters/ScenarioId' - - $ref: '#/components/parameters/IfMatch' - - $ref: '#/components/parameters/SimulationToken' - - $ref: '#/components/parameters/CorrelationId' - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/UpdateScenarioInput'} - responses: - '202': - description: Alteração aceite e reexecução colocada em fila - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/Scenario'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '404': {$ref: '#/components/responses/NotFound'} - '412': {$ref: '#/components/responses/PreconditionFailed'} - '422': {$ref: '#/components/responses/ValidationError'} - '428': {$ref: '#/components/responses/PreconditionRequired'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/simulations/{id}/jobs: - get: - tags: [Jobs] - operationId: listSimulationJobs - summary: Consultar jobs de execução e geração de relatórios - description: Contrato reservado para consulta dos jobs associados à simulação. - x-implementation-status: contract-only - security: - - sessionCookie: [] - - {} - parameters: - - $ref: '#/components/parameters/SimulationId' - - $ref: '#/components/parameters/SimulationToken' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Jobs visíveis no âmbito autorizado - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/JobCollection'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '404': {$ref: '#/components/responses/NotFound'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/simulations/{id}/reports/{reportId}: - get: - tags: [Relatórios] - operationId: getSimulationReport - summary: Obter snapshot imutável de relatório - x-implementation-status: implemented - security: - - sessionCookie: [] - - {} - parameters: - - $ref: '#/components/parameters/SimulationId' - - $ref: '#/components/parameters/ReportId' - - $ref: '#/components/parameters/SimulationToken' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Snapshot e hash do relatório - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/ReportResponse'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '404': {$ref: '#/components/responses/NotFound'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/legislation/sources: - get: - tags: [Legislação] - operationId: listLegislationSources - summary: Consultar fontes legais publicadas e versionadas - description: Contrato reservado para catálogo público de fontes; administração usa a API v2 existente. - x-implementation-status: contract-only - security: [] - parameters: - - $ref: '#/components/parameters/Page' - - $ref: '#/components/parameters/PerPage' - - $ref: '#/components/parameters/Search' - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Fontes legais publicadas - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/LegislationPage'} - '404': {$ref: '#/components/responses/NotFound'} - '422': {$ref: '#/components/responses/ValidationError'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v2/investment/admin/legal-sources: - get: - tags: [Administração, Legislação] - operationId: adminListLegalSources - summary: Listar fontes legais para administração - x-implementation-status: implemented - security: [{sessionCookie: []}] - parameters: - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Lista de fontes legais - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/AdminLegalSourcePage'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - post: - tags: [Administração, Legislação] - operationId: adminCreateLegalSource - summary: Registar fonte legal para validação - x-implementation-status: implemented - security: [{sessionCookie: []}] - parameters: - - $ref: '#/components/parameters/CorrelationId' - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/LegalSourceInput'} - responses: - '201': - description: Fonte legal registada - headers: - Location: {$ref: '#/components/headers/Location'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/LegalSource'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '409': {$ref: '#/components/responses/Conflict'} - '422': {$ref: '#/components/responses/ValidationError'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v2/investment/admin/legal-sources/{id}/{action}: - post: - tags: [Administração, Legislação] - operationId: adminTransitionLegalSource - summary: Validar ou revogar uma fonte legal - x-implementation-status: implemented - security: [{sessionCookie: []}] - parameters: - - $ref: '#/components/parameters/SourceId' - - name: action - in: path - required: true - schema: {type: string, enum: [validate, revoke]} - - $ref: '#/components/parameters/CorrelationId' - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/LegalSourceTransitionInput'} - responses: - '200': - description: Estado actualizado - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/LegalSource'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '404': {$ref: '#/components/responses/NotFound'} - '409': {$ref: '#/components/responses/Conflict'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/interest-requests: - post: - tags: [Manifestações] - operationId: submitInterestRequest - summary: Submeter manifestação de interesse - x-implementation-status: implemented - security: [] - parameters: - - $ref: '#/components/parameters/IdempotencyKeyOptional' - - $ref: '#/components/parameters/CorrelationId' - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/InterestRequestInput'} - responses: - '201': - description: Manifestação submetida - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/InterestRequestResponse'} - '401': {$ref: '#/components/responses/Unauthorized'} - '403': {$ref: '#/components/responses/Forbidden'} - '409': {$ref: '#/components/responses/Conflict'} - '422': {$ref: '#/components/responses/ValidationError'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} - /api/v1/opportunities/{slug}: - get: - tags: [Oportunidades] - operationId: getOpportunity - summary: Consultar oportunidade publicada - x-implementation-status: implemented - security: [] - parameters: - - name: slug - in: path - required: true - schema: {type: string, minLength: 1, maxLength: 180} - - $ref: '#/components/parameters/CorrelationId' - responses: - '200': - description: Oportunidade publicada - headers: - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/OpportunityResponse'} - '404': {$ref: '#/components/responses/NotFound'} - '429': {$ref: '#/components/responses/RateLimited'} - '500': {$ref: '#/components/responses/ServerError'} + /api/v1/catalogs/{catalog}: + get: + tags: [Catálogos] + operationId: listReferenceCatalog + summary: Consultar catálogo versionado + security: [] + parameters: + - $ref: '#/components/parameters/CatalogName' + - $ref: '#/components/parameters/Version' + - $ref: '#/components/parameters/CatalogType' + - $ref: '#/components/parameters/ParentId' + - $ref: '#/components/parameters/JurisdictionId' + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PerPage' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Página do catálogo na versão solicitada + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: { $ref: '#/components/schemas/CatalogPage' } + '404': { $ref: '#/components/responses/NotFound' } + '422': { $ref: '#/components/responses/ValidationError' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/questionnaires/{code}: + get: + tags: [Simulações] + operationId: getPublishedQuestionnaire + summary: Consultar questionário publicado e versão imutável + security: [] + parameters: + - $ref: '#/components/parameters/QuestionnaireCode' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Questionário publicado em pt-MZ + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/QuestionnaireResponse', + } + '404': { $ref: '#/components/responses/NotFound' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/simulations: + post: + tags: [Simulações] + operationId: createSimulation + summary: Criar simulação rastreável + description: Requer Idempotency-Key. Acesso anónimo requer token opaco gerado pelo cliente. + x-implementation-status: implemented + security: + - sessionCookie: [] + - {} + parameters: + - $ref: '#/components/parameters/IdempotencyKey' + - $ref: '#/components/parameters/SimulationToken' + - $ref: '#/components/parameters/CorrelationId' + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/SimulationInput' } + examples: + BoundaryValues: + summary: Exemplo técnico nos limites mínimos + description: Valores mínimos para demonstrar a forma do payload; não são valores fiscais recomendados. + value: { base: 0, rate: 0, reduction: 0 } + responses: + '202': + description: Simulação aceite para processamento + headers: + ETag: { $ref: '#/components/headers/ETag' } + Location: { $ref: '#/components/headers/Location' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/SimulationAccepted', + } + '200': + description: Repetição idempotente da resposta original + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/SimulationAccepted', + } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '409': { $ref: '#/components/responses/Conflict' } + '422': { $ref: '#/components/responses/ValidationError' } + '428': { $ref: '#/components/responses/PreconditionRequired' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/simulations/{id}: + get: + tags: [Simulações] + operationId: getSimulation + summary: Consultar simulação, cenários e estado de execução + x-implementation-status: implemented + security: + - sessionCookie: [] + - {} + parameters: + - $ref: '#/components/parameters/SimulationId' + - $ref: '#/components/parameters/SimulationToken' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Simulação dentro do âmbito autorizado + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/SimulationResponse', + } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/simulations/{id}/scenarios/{scenarioId}: + patch: + tags: [Simulações] + operationId: updateSimulationScenario + summary: Actualizar cenário com controlo optimista de concorrência + x-implementation-status: implemented + security: + - sessionCookie: [] + - {} + parameters: + - $ref: '#/components/parameters/SimulationId' + - $ref: '#/components/parameters/ScenarioId' + - $ref: '#/components/parameters/IfMatch' + - $ref: '#/components/parameters/SimulationToken' + - $ref: '#/components/parameters/CorrelationId' + requestBody: + required: true + content: + application/json: + schema: + { $ref: '#/components/schemas/UpdateScenarioInput' } + responses: + '202': + description: Alteração aceite e reexecução colocada em fila + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: { $ref: '#/components/schemas/Scenario' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '412': { $ref: '#/components/responses/PreconditionFailed' } + '422': { $ref: '#/components/responses/ValidationError' } + '428': { $ref: '#/components/responses/PreconditionRequired' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/simulations/{id}/jobs: + get: + tags: [Jobs] + operationId: listSimulationJobs + summary: Consultar jobs de execução e geração de relatórios + description: Contrato reservado para consulta dos jobs associados à simulação. + x-implementation-status: contract-only + security: + - sessionCookie: [] + - {} + parameters: + - $ref: '#/components/parameters/SimulationId' + - $ref: '#/components/parameters/SimulationToken' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Jobs visíveis no âmbito autorizado + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { $ref: '#/components/schemas/JobCollection' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/simulations/{id}/reports/{reportId}: + get: + tags: [Relatórios] + operationId: getSimulationReport + summary: Obter snapshot imutável de relatório + x-implementation-status: implemented + security: + - sessionCookie: [] + - {} + parameters: + - $ref: '#/components/parameters/SimulationId' + - $ref: '#/components/parameters/ReportId' + - $ref: '#/components/parameters/SimulationToken' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Snapshot e hash do relatório + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { $ref: '#/components/schemas/ReportResponse' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/legislation/sources: + get: + tags: [Legislação] + operationId: listLegislationSources + summary: Consultar fontes legais publicadas e versionadas + description: Contrato reservado para catálogo público de fontes; administração usa a API v2 existente. + x-implementation-status: contract-only + security: [] + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PerPage' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Fontes legais publicadas + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { $ref: '#/components/schemas/LegislationPage' } + '404': { $ref: '#/components/responses/NotFound' } + '422': { $ref: '#/components/responses/ValidationError' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v2/investment/admin/legal-sources: + get: + tags: [Administração, Legislação] + operationId: adminListLegalSources + summary: Listar fontes legais para administração + x-implementation-status: implemented + security: [{ sessionCookie: [] }] + parameters: + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Lista de fontes legais + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/AdminLegalSourcePage', + } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + post: + tags: [Administração, Legislação] + operationId: adminCreateLegalSource + summary: Registar fonte legal para validação + x-implementation-status: implemented + security: [{ sessionCookie: [] }] + parameters: + - $ref: '#/components/parameters/CorrelationId' + requestBody: + required: true + content: + application/json: + schema: + { $ref: '#/components/schemas/LegalSourceInput' } + responses: + '201': + description: Fonte legal registada + headers: + Location: { $ref: '#/components/headers/Location' } + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: { $ref: '#/components/schemas/LegalSource' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '409': { $ref: '#/components/responses/Conflict' } + '422': { $ref: '#/components/responses/ValidationError' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v2/investment/admin/legal-sources/{id}/{action}: + post: + tags: [Administração, Legislação] + operationId: adminTransitionLegalSource + summary: Validar ou revogar uma fonte legal + x-implementation-status: implemented + security: [{ sessionCookie: [] }] + parameters: + - $ref: '#/components/parameters/SourceId' + - name: action + in: path + required: true + schema: { type: string, enum: [validate, revoke] } + - $ref: '#/components/parameters/CorrelationId' + requestBody: + required: true + content: + application/json: + schema: + { + $ref: '#/components/schemas/LegalSourceTransitionInput', + } + responses: + '200': + description: Estado actualizado + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: { $ref: '#/components/schemas/LegalSource' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '409': { $ref: '#/components/responses/Conflict' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/interest-requests: + post: + tags: [Manifestações] + operationId: submitInterestRequest + summary: Submeter manifestação de interesse + x-implementation-status: implemented + security: [] + parameters: + - $ref: '#/components/parameters/IdempotencyKeyOptional' + - $ref: '#/components/parameters/CorrelationId' + requestBody: + required: true + content: + application/json: + schema: + { + $ref: '#/components/schemas/InterestRequestInput', + } + responses: + '201': + description: Manifestação submetida + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/InterestRequestResponse', + } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '409': { $ref: '#/components/responses/Conflict' } + '422': { $ref: '#/components/responses/ValidationError' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } + /api/v1/opportunities/{slug}: + get: + tags: [Oportunidades] + operationId: getOpportunity + summary: Consultar oportunidade publicada + x-implementation-status: implemented + security: [] + parameters: + - name: slug + in: path + required: true + schema: { type: string, minLength: 1, maxLength: 180 } + - $ref: '#/components/parameters/CorrelationId' + responses: + '200': + description: Oportunidade publicada + headers: + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: + { + $ref: '#/components/schemas/OpportunityResponse', + } + '404': { $ref: '#/components/responses/NotFound' } + '429': { $ref: '#/components/responses/RateLimited' } + '500': { $ref: '#/components/responses/ServerError' } components: - securitySchemes: - sessionCookie: - type: apiKey - in: cookie - name: laravel-session - description: Sessão Laravel; o nome pode ser substituído por SESSION_COOKIE. Rotas públicas e simulações anónimas substituem explicitamente este requisito. - parameters: - CatalogName: - name: catalog - in: path - required: true - schema: {type: string, enum: [authorities, jurisdictions, economic-activities]} - Version: - name: version - in: query - required: false - description: Versão estável publicada do catálogo - schema: {type: integer, minimum: 1} - CatalogType: - name: type - in: query - required: false - description: Obrigatório para jurisdictions. - schema: {type: string, enum: [country, province, district, admin_post, locality]} - ParentId: - name: parent_id - in: query - schema: {type: string, format: uuid} - JurisdictionId: - name: jurisdiction_id - in: query - schema: {type: string, format: uuid} - Page: - name: page - in: query - schema: {type: integer, minimum: 1, default: 1} - PerPage: - name: per_page - in: query - schema: {type: integer, minimum: 1, maximum: 100, default: 20} - Search: - name: q - in: query - schema: {type: string, maxLength: 100} - QuestionnaireCode: - name: code - in: path - required: true - schema: {type: string, pattern: '^[a-z0-9][a-z0-9-]{0,79}$'} - SimulationId: - name: id - in: path - required: true - schema: {type: string, format: uuid} - ScenarioId: - name: scenarioId - in: path - required: true - schema: {type: string, format: uuid} - ReportId: - name: reportId - in: path - required: true - schema: {type: string, format: uuid} - SourceId: - name: id - in: path - required: true - schema: {type: string, format: uuid} - IdempotencyKey: - name: Idempotency-Key - in: header - required: true - description: Repetições com a mesma chave e payload devolvem a resposta original; payload diferente produz conflito. - schema: {type: string, minLength: 16, maxLength: 128} - IdempotencyKeyOptional: - name: Idempotency-Key - in: header - required: false - description: Se fornecida, deve ser UUID; uma chave repetida com outro payload produz conflito. - schema: {type: string, format: uuid} - SimulationToken: - name: X-Simulation-Token - in: header - required: false - description: Obrigatório para acesso anónimo; token opaco do cliente, guardado apenas como hash. - schema: {type: string, minLength: 32, maxLength: 256} - IfMatch: - name: If-Match - in: header - required: true - description: ETag forte da representação lida previamente. - schema: {type: string, minLength: 1, maxLength: 128} - CorrelationId: - name: X-Correlation-ID - in: header - required: false - description: UUID opcional propagado pela aplicação; quando ausente, a aplicação cria um identificador. - schema: {type: string, format: uuid} - headers: - ETag: - description: Validador da representação; usado com If-Match em alterações concorrentes. - schema: {type: string} - Location: - description: URI relativa do recurso criado ou aceite. - schema: {type: string, format: uri-reference} - RetryAfter: - description: Segundos até à próxima tentativa. - schema: {type: integer, minimum: 0} - CorrelationId: - description: Identificador de correlação da operação. - schema: {type: string, format: uuid} - responses: - Unauthorized: - description: Autenticação necessária ou sessão inválida - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - Forbidden: - description: Actor autenticado sem autorização para a operação - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - NotFound: - description: Recurso inexistente ou fora do âmbito visível - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - Conflict: - description: Conflito de estado, versão ou chave idempotente - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: - application/json: - schema: {$ref: '#/components/schemas/Problem'} - example: {message: A chave foi reutilizada com conteúdo diferente., code: idempotency_conflict} - PreconditionFailed: - description: If-Match não corresponde ao ETag actual - headers: - ETag: {$ref: '#/components/headers/ETag'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: - application/json: - schema: {$ref: '#/components/schemas/Problem'} - example: {message: A representação mudou; volte a consultar o recurso., code: stale_etag} - ValidationError: - description: Dados inválidos - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/ValidationProblem'}}} - RateLimited: - description: Limite de pedidos excedido - headers: - Retry-After: {$ref: '#/components/headers/RetryAfter'} - X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - ServerError: - description: Erro interno; resposta não contém detalhes sensíveis - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - PreconditionRequired: - description: Header obrigatório em falta, como If-Match ou Idempotency-Key - headers: {X-Correlation-ID: {$ref: '#/components/headers/CorrelationId'}} - content: {application/json: {schema: {$ref: '#/components/schemas/Problem'}}} - schemas: - Problem: - type: object - required: [message] - properties: - message: {type: string} - code: {type: string} - correlation_id: {type: string, format: uuid} - additionalProperties: true - ValidationProblem: - allOf: - - $ref: '#/components/schemas/Problem' - - type: object - properties: - errors: - type: object - additionalProperties: - type: array - items: {type: string} - CatalogItem: - type: object - required: [id, value, code, name, label, locale, version_no] - properties: - id: {type: string, format: uuid} - value: {type: string} - code: {type: string} - name: {type: string, description: Rótulo apresentado em português de Moçambique.} - label: {type: string} - locale: {type: string, const: pt-MZ} - parent_id: {type: [string, 'null']} - type: {type: [string, 'null'], enum: [country, province, district, admin_post, locality, 'null']} - authority_type: {type: [string, 'null']} - organization_id: {type: [string, 'null'], format: uuid} - jurisdiction_id: {type: [string, 'null'], format: uuid} - jurisdiction_name: {type: [string, 'null']} - source_code: {type: [string, 'null']} - sector_id: {type: [string, 'null']} - version_no: {type: integer} - CatalogPage: - type: object - required: [data, meta] - properties: - data: {type: array, items: {$ref: '#/components/schemas/CatalogItem'}} - links: - type: object - properties: - next: {type: [string, 'null'], format: uri-reference} - previous: {type: [string, 'null'], format: uri-reference} - next_page_url: {type: [string, 'null'], format: uri-reference} - prev_page_url: {type: [string, 'null'], format: uri-reference} - current_page: {type: integer} - per_page: {type: integer} - total: {type: integer} - last_page: {type: integer} - meta: - allOf: - - $ref: '#/components/schemas/Pagination' - - type: object - properties: - catalog: {type: string} - version: {type: [integer, 'null']} - locale: {type: string, const: pt-MZ} - source_version: {type: [string, 'null']} - status: {type: string, enum: [published, not_published]} - Pagination: - type: object - required: [current_page, per_page, total] - properties: - current_page: {type: integer, minimum: 1} - per_page: {type: integer, minimum: 1} - total: {type: integer, minimum: 0} - version: {type: string} - QuestionnaireResponse: - type: object - required: [data] - properties: - data: - type: object - required: [id, code, version_no, locale, schema_snapshot, status] - properties: - id: {type: string, format: uuid} - code: {type: string} - version_no: {type: integer, minimum: 1} - locale: {type: string, const: pt-MZ} - schema_snapshot: {type: object, additionalProperties: true} - status: {type: string, const: published} - valid_from: {type: [string, 'null'], format: date} - valid_to: {type: [string, 'null'], format: date} - SimulationInput: - type: object - required: [base, rate] - properties: - base: {type: number, minimum: 0, description: Valor de entrada sem moeda inferida.} - rate: {type: number, minimum: 0, maximum: 100, description: Parâmetro fornecido pelo utilizador; não representa taxa oficial.} - reduction: {type: number, minimum: 0, maximum: 100, default: 0} - organization_id: {type: [string, 'null'], format: uuid} - additionalProperties: false - UpdateScenarioInput: - type: object - required: [label, base, rate] - properties: - label: {type: string, minLength: 1, maxLength: 100} - base: {type: number, minimum: 0} - rate: {type: number, minimum: 0, maximum: 100} - reduction: {type: number, minimum: 0, maximum: 100, default: 0} - additionalProperties: false - SimulationAccepted: - type: object - required: [data, replayed] - properties: - data: {$ref: '#/components/schemas/Simulation'} - replayed: {type: boolean} - access_token: {type: string, minLength: 32, maxLength: 256, description: Devolvido apenas para execução anónima.} - SimulationResponse: - type: object - required: [data] - properties: - data: {$ref: '#/components/schemas/Simulation'} - Simulation: - type: object - required: [id, status, version_no, created_at, updated_at] - properties: - id: {type: string, format: uuid} - questionnaire_version_id: {type: [string, 'null'], format: uuid} - organization_id: {type: [string, 'null'], format: uuid} - version_no: {type: integer, minimum: 1} - status: {type: string, enum: [queued, running, completed, failed, cancelled]} - input_snapshot: {type: object, additionalProperties: true} - result_snapshot: {type: [object, 'null'], additionalProperties: true} - calculation_snapshot: {type: [object, 'null'], additionalProperties: true} - scenarios: {type: array, items: {$ref: '#/components/schemas/Scenario'}} - created_at: {type: string, format: date-time} - updated_at: {type: string, format: date-time} - Scenario: - type: object - required: [id, code, label, version_no, parameters] - properties: - id: {type: string, format: uuid} - code: {type: string} - label: {type: string} - version_no: {type: integer, minimum: 1} - parameters: {type: object, additionalProperties: true} - JobCollection: - type: object - required: [data] - properties: - data: {type: array, items: {$ref: '#/components/schemas/SimulationJob'}} - SimulationJob: - type: object - required: [id, simulation_id, job_type, status, attempts, available_at] - properties: - id: {type: string, format: uuid} - simulation_id: {type: string, format: uuid} - job_type: {type: string, enum: [execute, report]} - status: {type: string, enum: [pending, running, succeeded, failed]} - attempts: {type: integer, minimum: 0, maximum: 3} - available_at: {type: string, format: date-time} - started_at: {type: [string, 'null'], format: date-time} - finished_at: {type: [string, 'null'], format: date-time} - error_code: {type: [string, 'null']} - ReportResponse: - type: object - required: [data] - properties: - data: {$ref: '#/components/schemas/SimulationReport'} - SimulationReport: - type: object - required: [id, simulation_id, version_no, status] - properties: - id: {type: string, format: uuid} - simulation_id: {type: string, format: uuid} - version_no: {type: integer, minimum: 1} - status: {type: string, enum: [queued, ready, failed]} - report_snapshot: {type: [object, 'null'], additionalProperties: true} - report_sha256: {type: [string, 'null'], pattern: '^[0-9a-f]{64}$'} - LegislationPage: - type: object - required: [data, meta] - properties: - data: {type: array, items: {$ref: '#/components/schemas/LegalSource'}} - meta: {$ref: '#/components/schemas/Pagination'} - AdminLegalSourcePage: - type: object - required: [data] - properties: - data: {type: array, items: {$ref: '#/components/schemas/LegalSource'}} - current_page: {type: integer} - per_page: {type: integer} - total: {type: integer} - last_page: {type: integer} - first_page_url: {type: [string, 'null'], format: uri-reference} - last_page_url: {type: [string, 'null'], format: uri-reference} - next_page_url: {type: [string, 'null'], format: uri-reference} - prev_page_url: {type: [string, 'null'], format: uri-reference} - LegalSourceInput: - type: object - required: [title, url, diploma, article, competent_entity, valid_from] - properties: - title: {type: string, minLength: 1, maxLength: 255} - url: {type: string, format: uri, pattern: '^https://', maxLength: 2000} - diploma: {type: string, maxLength: 255} - article: {type: string, maxLength: 255} - competent_entity: {type: string, maxLength: 255} - valid_from: {type: string, format: date} - valid_until: {type: [string, 'null'], format: date} - additionalProperties: false - LegalSourceTransitionInput: - type: object - required: [version_no] - properties: - version_no: {type: integer, minimum: 1} - additionalProperties: false - LegalSource: - type: object - properties: - id: {type: string, format: uuid} - title: {type: string} - url: {type: string, format: uri} - diploma: {type: string} - article: {type: string} - competent_entity: {type: string} - validation_status: {type: string, enum: [pending, validated, revoked]} - version_no: {type: integer} - valid_until: {type: [string, 'null'], format: date} - validator_id: {type: [integer, 'null']} - validated_at: {type: [string, 'null'], format: date-time} - valid_from: {type: [string, 'null'], format: date} - InterestRequestInput: - type: object - required: [fullName, email, investorType, country, sectorInterest, message, privacyConsent] - properties: - fullName: {type: string, minLength: 3, maxLength: 150, writeOnly: true} - email: {type: string, format: email, maxLength: 150, writeOnly: true} - phone: {type: [string, 'null'], maxLength: 30, writeOnly: true} - investorType: {type: string, enum: [individual, empresa, fundo, entidade_publica, outro]} - country: {type: string, maxLength: 100} - sectorInterest: {type: string, maxLength: 150} - opportunityReference: {type: [string, 'null'], maxLength: 80} - existingBusinessId: {type: [string, 'null'], format: uuid} - modality: {type: [string, 'null'], maxLength: 50} - investmentRange: {type: [string, 'null'], maxLength: 80} - message: {type: string, minLength: 20, maxLength: 2000, writeOnly: true} - privacyConsent: {type: boolean, const: true} - shareWithProponent: {type: boolean, default: false} - additionalProperties: false - not: - allOf: - - required: [opportunityReference] + securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: laravel-session + description: Sessão Laravel; o nome pode ser substituído por SESSION_COOKIE. Rotas públicas e simulações anónimas substituem explicitamente este requisito. + parameters: + CatalogName: + name: catalog + in: path + required: true + schema: + { + type: string, + enum: [authorities, jurisdictions, economic-activities], + } + Version: + name: version + in: query + required: false + description: Versão estável publicada do catálogo + schema: { type: integer, minimum: 1 } + CatalogType: + name: type + in: query + required: false + description: Obrigatório para jurisdictions. + schema: + { + type: string, + enum: [country, province, district, admin_post, locality], + } + ParentId: + name: parent_id + in: query + schema: { type: string, format: uuid } + JurisdictionId: + name: jurisdiction_id + in: query + schema: { type: string, format: uuid } + Page: + name: page + in: query + schema: { type: integer, minimum: 1, default: 1 } + PerPage: + name: per_page + in: query + schema: { type: integer, minimum: 1, maximum: 100, default: 20 } + Search: + name: q + in: query + schema: { type: string, maxLength: 100 } + QuestionnaireCode: + name: code + in: path + required: true + schema: { type: string, pattern: '^[a-z0-9][a-z0-9-]{0,79}$' } + SimulationId: + name: id + in: path + required: true + schema: { type: string, format: uuid } + ScenarioId: + name: scenarioId + in: path + required: true + schema: { type: string, format: uuid } + ReportId: + name: reportId + in: path + required: true + schema: { type: string, format: uuid } + SourceId: + name: id + in: path + required: true + schema: { type: string, format: uuid } + IdempotencyKey: + name: Idempotency-Key + in: header + required: true + description: Repetições com a mesma chave e payload devolvem a resposta original; payload diferente produz conflito. + schema: { type: string, minLength: 16, maxLength: 128 } + IdempotencyKeyOptional: + name: Idempotency-Key + in: header + required: false + description: Se fornecida, deve ser UUID; uma chave repetida com outro payload produz conflito. + schema: { type: string, format: uuid } + SimulationToken: + name: X-Simulation-Token + in: header + required: false + description: Obrigatório para acesso anónimo; token opaco do cliente, guardado apenas como hash. + schema: { type: string, minLength: 32, maxLength: 256 } + IfMatch: + name: If-Match + in: header + required: true + description: ETag forte da representação lida previamente. + schema: { type: string, minLength: 1, maxLength: 128 } + CorrelationId: + name: X-Correlation-ID + in: header + required: false + description: UUID opcional propagado pela aplicação; quando ausente, a aplicação cria um identificador. + schema: { type: string, format: uuid } + headers: + ETag: + description: Validador da representação; usado com If-Match em alterações concorrentes. + schema: { type: string } + Location: + description: URI relativa do recurso criado ou aceite. + schema: { type: string, format: uri-reference } + RetryAfter: + description: Segundos até à próxima tentativa. + schema: { type: integer, minimum: 0 } + CorrelationId: + description: Identificador de correlação da operação. + schema: { type: string, format: uuid } + responses: + Unauthorized: + description: Autenticação necessária ou sessão inválida + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + Forbidden: + description: Actor autenticado sem autorização para a operação + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + NotFound: + description: Recurso inexistente ou fora do âmbito visível + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + Conflict: + description: Conflito de estado, versão ou chave idempotente + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + application/json: + schema: { $ref: '#/components/schemas/Problem' } + example: + { + message: A chave foi reutilizada com conteúdo diferente., + code: idempotency_conflict, + } + PreconditionFailed: + description: If-Match não corresponde ao ETag actual + headers: + ETag: { $ref: '#/components/headers/ETag' } + X-Correlation-ID: { $ref: '#/components/headers/CorrelationId' } + content: + application/json: + schema: { $ref: '#/components/schemas/Problem' } + example: + { + message: A representação mudou; volte a consultar o recurso., + code: stale_etag, + } + ValidationError: + description: Dados inválidos + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { + schema: + { + $ref: '#/components/schemas/ValidationProblem', + }, + }, + } + RateLimited: + description: Limite de pedidos excedido + headers: + Retry-After: { $ref: '#/components/headers/RetryAfter' } + X-Correlation-ID: { $ref: '#/components/headers/CorrelationId' } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + ServerError: + description: Erro interno; resposta não contém detalhes sensíveis + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + PreconditionRequired: + description: Header obrigatório em falta, como If-Match ou Idempotency-Key + headers: + { + X-Correlation-ID: + { $ref: '#/components/headers/CorrelationId' }, + } + content: + { + application/json: + { schema: { $ref: '#/components/schemas/Problem' } }, + } + schemas: + Problem: + type: object + required: [message] properties: - opportunityReference: {type: string, minLength: 1} - - required: [existingBusinessId] + message: { type: string } + code: { type: string } + correlation_id: { type: string, format: uuid } + additionalProperties: true + ValidationProblem: + allOf: + - $ref: '#/components/schemas/Problem' + - type: object + properties: + errors: + type: object + additionalProperties: + type: array + items: { type: string } + CatalogItem: + type: object + required: [id, value, code, name, label, locale, version_no] properties: - existingBusinessId: {type: string, format: uuid} - allOf: - - if: - required: [existingBusinessId] + id: { type: string, format: uuid } + value: { type: string } + code: { type: string } + name: + { + type: string, + description: Rótulo apresentado em português de Moçambique., + } + label: { type: string } + locale: { type: string, const: pt-MZ } + parent_id: { type: [string, 'null'] } + type: + { + type: [string, 'null'], + enum: + [ + country, + province, + district, + admin_post, + locality, + 'null', + ], + } + authority_type: { type: [string, 'null'] } + organization_id: { type: [string, 'null'], format: uuid } + jurisdiction_id: { type: [string, 'null'], format: uuid } + jurisdiction_name: { type: [string, 'null'] } + source_code: { type: [string, 'null'] } + sector_id: { type: [string, 'null'] } + version_no: { type: integer } + CatalogPage: + type: object + required: [data, meta] properties: - existingBusinessId: {type: string} - then: - required: [modality] + data: + { + type: array, + items: { $ref: '#/components/schemas/CatalogItem' }, + } + links: + type: object + properties: + next: { type: [string, 'null'], format: uri-reference } + previous: + { type: [string, 'null'], format: uri-reference } + next_page_url: { type: [string, 'null'], format: uri-reference } + prev_page_url: { type: [string, 'null'], format: uri-reference } + current_page: { type: integer } + per_page: { type: integer } + total: { type: integer } + last_page: { type: integer } + meta: + allOf: + - $ref: '#/components/schemas/Pagination' + - type: object + properties: + catalog: { type: string } + version: { type: [integer, 'null'] } + locale: { type: string, const: pt-MZ } + source_version: { type: [string, 'null'] } + status: + { + type: string, + enum: [published, not_published], + } + Pagination: + type: object + required: [current_page, per_page, total] properties: - modality: {type: string, minLength: 1, maxLength: 50} - InterestRequestResponse: - type: object - required: [success, message, data] - properties: - success: {type: boolean, const: true} - message: {type: string} - data: - type: object - required: [reference, status] - properties: - reference: {type: string} - status: {type: string, const: received} - OpportunityResponse: - type: object - required: [success, data] - properties: - success: {type: boolean, const: true} - data: - type: object - required: [id, slug, title] - properties: - id: {type: [string, integer]} - slug: {type: string} - title: {type: string} - summary: {type: [string, 'null']} - status: {type: string} + current_page: { type: integer, minimum: 1 } + per_page: { type: integer, minimum: 1 } + total: { type: integer, minimum: 0 } + version: { type: string } + QuestionnaireResponse: + type: object + required: [data] + properties: + data: + type: object + required: + [id, code, version_no, locale, schema_snapshot, status] + properties: + id: { type: string, format: uuid } + code: { type: string } + version_no: { type: integer, minimum: 1 } + locale: { type: string, const: pt-MZ } + schema_snapshot: + { type: object, additionalProperties: true } + status: { type: string, const: published } + valid_from: { type: [string, 'null'], format: date } + valid_to: { type: [string, 'null'], format: date } + SimulationInput: + type: object + required: [base, rate] + properties: + base: + { + type: number, + minimum: 0, + description: Valor de entrada sem moeda inferida., + } + rate: + { + type: number, + minimum: 0, + maximum: 100, + description: Parâmetro fornecido pelo utilizador; não representa taxa oficial., + } + reduction: + { type: number, minimum: 0, maximum: 100, default: 0 } + organization_id: { type: [string, 'null'], format: uuid } + additionalProperties: false + UpdateScenarioInput: + type: object + required: [label, base, rate] + properties: + label: { type: string, minLength: 1, maxLength: 100 } + base: { type: number, minimum: 0 } + rate: { type: number, minimum: 0, maximum: 100 } + reduction: + { type: number, minimum: 0, maximum: 100, default: 0 } + additionalProperties: false + SimulationAccepted: + type: object + required: [data, replayed] + properties: + data: { $ref: '#/components/schemas/Simulation' } + replayed: { type: boolean } + access_token: + { + type: string, + minLength: 32, + maxLength: 256, + description: Devolvido apenas para execução anónima., + } + SimulationResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/Simulation' } + Simulation: + type: object + required: [id, status, version_no, created_at, updated_at] + properties: + id: { type: string, format: uuid } + questionnaire_version_id: + { type: [string, 'null'], format: uuid } + organization_id: { type: [string, 'null'], format: uuid } + version_no: { type: integer, minimum: 1 } + status: + { + type: string, + enum: [queued, running, completed, failed, cancelled], + } + input_snapshot: { type: object, additionalProperties: true } + result_snapshot: + { type: [object, 'null'], additionalProperties: true } + calculation_snapshot: + { type: [object, 'null'], additionalProperties: true } + scenarios: + { + type: array, + items: { $ref: '#/components/schemas/Scenario' }, + } + created_at: { type: string, format: date-time } + updated_at: { type: string, format: date-time } + Scenario: + type: object + required: [id, code, label, version_no, parameters] + properties: + id: { type: string, format: uuid } + code: { type: string } + label: { type: string } + version_no: { type: integer, minimum: 1 } + parameters: { type: object, additionalProperties: true } + JobCollection: + type: object + required: [data] + properties: + data: + { + type: array, + items: { $ref: '#/components/schemas/SimulationJob' }, + } + SimulationJob: + type: object + required: + [id, simulation_id, job_type, status, attempts, available_at] + properties: + id: { type: string, format: uuid } + simulation_id: { type: string, format: uuid } + job_type: { type: string, enum: [execute, report] } + status: + { + type: string, + enum: [pending, running, succeeded, failed], + } + attempts: { type: integer, minimum: 0, maximum: 3 } + available_at: { type: string, format: date-time } + started_at: { type: [string, 'null'], format: date-time } + finished_at: { type: [string, 'null'], format: date-time } + error_code: { type: [string, 'null'] } + ReportResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/SimulationReport' } + SimulationReport: + type: object + required: [id, simulation_id, version_no, status] + properties: + id: { type: string, format: uuid } + simulation_id: { type: string, format: uuid } + version_no: { type: integer, minimum: 1 } + status: { type: string, enum: [queued, ready, failed] } + report_snapshot: + { type: [object, 'null'], additionalProperties: true } + report_sha256: + { type: [string, 'null'], pattern: '^[0-9a-f]{64}$' } + LegislationPage: + type: object + required: [data, meta] + properties: + data: + { + type: array, + items: { $ref: '#/components/schemas/LegalSource' }, + } + meta: { $ref: '#/components/schemas/Pagination' } + AdminLegalSourcePage: + type: object + required: [data] + properties: + data: + { + type: array, + items: { $ref: '#/components/schemas/LegalSource' }, + } + current_page: { type: integer } + per_page: { type: integer } + total: { type: integer } + last_page: { type: integer } + first_page_url: + { type: [string, 'null'], format: uri-reference } + last_page_url: { type: [string, 'null'], format: uri-reference } + next_page_url: { type: [string, 'null'], format: uri-reference } + prev_page_url: { type: [string, 'null'], format: uri-reference } + LegalSourceInput: + type: object + required: + [title, url, diploma, article, competent_entity, valid_from] + properties: + title: { type: string, minLength: 1, maxLength: 255 } + url: + { + type: string, + format: uri, + pattern: '^https://', + maxLength: 2000, + } + diploma: { type: string, maxLength: 255 } + article: { type: string, maxLength: 255 } + competent_entity: { type: string, maxLength: 255 } + valid_from: { type: string, format: date } + valid_until: { type: [string, 'null'], format: date } + additionalProperties: false + LegalSourceTransitionInput: + type: object + required: [version_no] + properties: + version_no: { type: integer, minimum: 1 } + additionalProperties: false + LegalSource: + type: object + properties: + id: { type: string, format: uuid } + title: { type: string } + url: { type: string, format: uri } + diploma: { type: string } + article: { type: string } + competent_entity: { type: string } + validation_status: + { type: string, enum: [pending, validated, revoked] } + version_no: { type: integer } + valid_until: { type: [string, 'null'], format: date } + validator_id: { type: [integer, 'null'] } + validated_at: { type: [string, 'null'], format: date-time } + valid_from: { type: [string, 'null'], format: date } + InterestRequestInput: + type: object + required: + [ + fullName, + email, + investorType, + country, + sectorInterest, + message, + privacyConsent, + ] + properties: + fullName: + { + type: string, + minLength: 3, + maxLength: 150, + writeOnly: true, + } + email: + { + type: string, + format: email, + maxLength: 150, + writeOnly: true, + } + phone: + { type: [string, 'null'], maxLength: 30, writeOnly: true } + investorType: + { + type: string, + enum: + [ + individual, + empresa, + fundo, + entidade_publica, + outro, + ], + } + country: { type: string, maxLength: 100 } + sectorInterest: { type: string, maxLength: 150 } + opportunityReference: { type: [string, 'null'], maxLength: 80 } + existingBusinessId: { type: [string, 'null'], format: uuid } + modality: { type: [string, 'null'], maxLength: 50 } + investmentRange: { type: [string, 'null'], maxLength: 80 } + message: + { + type: string, + minLength: 20, + maxLength: 2000, + writeOnly: true, + } + privacyConsent: { type: boolean, const: true } + shareWithProponent: { type: boolean, default: false } + additionalProperties: false + not: + allOf: + - required: [opportunityReference] + properties: + opportunityReference: { type: string, minLength: 1 } + - required: [existingBusinessId] + properties: + existingBusinessId: { type: string, format: uuid } + allOf: + - if: + required: [existingBusinessId] + properties: + existingBusinessId: { type: string } + then: + required: [modality] + properties: + modality: + { type: string, minLength: 1, maxLength: 50 } + InterestRequestResponse: + type: object + required: [success, message, data] + properties: + success: { type: boolean, const: true } + message: { type: string } + data: + type: object + required: [reference, status] + properties: + reference: { type: string } + status: { type: string, const: received } + OpportunityResponse: + type: object + required: [success, data] + properties: + success: { type: boolean, const: true } + data: + type: object + required: [id, slug, title] + properties: + id: { type: [string, integer] } + slug: { type: string } + title: { type: string } + summary: { type: [string, 'null'] } + status: { type: string } diff --git a/app/Console/Commands/CheckDocumentLibrarySchema.php b/app/Console/Commands/CheckDocumentLibrarySchema.php index e5948d87..ddc16fb2 100644 --- a/app/Console/Commands/CheckDocumentLibrarySchema.php +++ b/app/Console/Commands/CheckDocumentLibrarySchema.php @@ -46,6 +46,7 @@ class CheckDocumentLibrarySchema extends Command foreach ($required as $table => $columns) { if (! Schema::hasTable($table)) { $missing[] = "tabela {$table}"; + continue; } diff --git a/app/Console/Commands/CheckMalwareScanner.php b/app/Console/Commands/CheckMalwareScanner.php index 6c06ee3c..e494754d 100644 --- a/app/Console/Commands/CheckMalwareScanner.php +++ b/app/Console/Commands/CheckMalwareScanner.php @@ -14,7 +14,7 @@ class CheckMalwareScanner extends Command public function handle(MalwareScanner $scanner): int { $health = $scanner->health(); - $this->line('Scanner: '.($health['engine'] ?? 'ClamAV')); + $this->line('Scanner: '.($health['engine'])); $this->line('Available: '.($health['available'] ? 'yes' : 'no')); $this->line('Engine version: '.($health['engine_version'] ?? 'unavailable')); $this->line('Signatures UTC: '.($health['signatures_at']?->format(DATE_ATOM) ?? 'unavailable')); diff --git a/app/Console/Commands/ImportDocumentBatch.php b/app/Console/Commands/ImportDocumentBatch.php index fbed1729..071ffddd 100644 --- a/app/Console/Commands/ImportDocumentBatch.php +++ b/app/Console/Commands/ImportDocumentBatch.php @@ -131,7 +131,7 @@ class ImportDocumentBatch extends Command } try { - $resourceId = DB::transaction(function () use ($validated, $documentId, $storageKey, $hash, $identity, $disk, $actorId): int { + $resourceId = DB::transaction(function () use ($validated, $documentId, $storageKey, $hash, $identity, $actorId): int { DB::select('SELECT pg_advisory_xact_lock(hashtext(?))', ['document-batch:'.$hash]); DB::select('SELECT pg_advisory_xact_lock(hashtext(?))', ['document-batch-identity:'.$identity]); if (DB::table('documents')->where('sha256', $hash)->whereNull('deleted_at')->exists()) { diff --git a/app/Console/Commands/ImportIndustrialParkCandidates.php b/app/Console/Commands/ImportIndustrialParkCandidates.php index 1c1d992f..a68261fb 100644 --- a/app/Console/Commands/ImportIndustrialParkCandidates.php +++ b/app/Console/Commands/ImportIndustrialParkCandidates.php @@ -17,7 +17,7 @@ class ImportIndustrialParkCandidates extends Command public function handle(): int { - $document = json_decode(file_get_contents(database_path('data/industrial-park-candidate-drafts.json')), true, flags: JSON_THROW_ON_ERROR); + $document = json_decode($this->readSource(database_path('data/industrial-park-candidate-drafts.json')), true, flags: JSON_THROW_ON_ERROR); if (($document['source_document_attached'] ?? null) !== true || ($document['source_document_reconciled'] ?? null) !== true) { $this->error('The source document must be attached and reconciled before importing drafts. No records written.'); @@ -106,4 +106,14 @@ class ImportIndustrialParkCandidates extends Command { return Str::upper(Str::ascii(trim($value))); } + + private function readSource(string $path): string + { + $bytes = file_get_contents($path); + if ($bytes === false) { + throw new RuntimeException('Source file is unavailable: '.$path); + } + + return $bytes; + } } diff --git a/app/Console/Commands/ImportInvestmentZones.php b/app/Console/Commands/ImportInvestmentZones.php index 26d55832..113bf36c 100644 --- a/app/Console/Commands/ImportInvestmentZones.php +++ b/app/Console/Commands/ImportInvestmentZones.php @@ -17,7 +17,7 @@ class ImportInvestmentZones extends Command public function handle(): int { $path = database_path('data/investment-zones-initial.json'); - $document = json_decode(file_get_contents($path), true, flags: JSON_THROW_ON_ERROR); + $document = json_decode($this->readSource($path), true, flags: JSON_THROW_ON_ERROR); $rows = []; foreach ($document['records'] as $record) { $existing = DB::table('investment_zones')->where('code', $record['code'])->first(['id', 'name', 'category', 'publication_status']); @@ -59,12 +59,12 @@ class ImportInvestmentZones extends Command continue; } - $record = collect($document['records'])->firstWhere('code', $row['code']); + $record = collect(is_array($document['records'] ?? null) ? $document['records'] : [])->firstWhere('code', $row['code']); $requiresLegalSource = in_array($record['category'], ['zee', 'zfi'], true); $source = $requiresLegalSource ? DB::table('investment_legal_sources')->where('url', $record['source']['url'])->where('diploma', $record['source']['diploma'])->first() : null; - $sourceId = $requiresLegalSource ? ($source?->id ?? (string) Str::uuid()) : null; + $sourceId = $requiresLegalSource ? ($source->id ?? (string) Str::uuid()) : null; if ($requiresLegalSource && ! $source) { DB::table('investment_legal_sources')->insert([ 'id' => $sourceId, ...$record['source'], 'validation_status' => 'pending', 'owner_id' => $actor, @@ -120,4 +120,14 @@ class ImportInvestmentZones extends Command { return Str::upper(Str::ascii(trim($value))); } + + private function readSource(string $path): string + { + $bytes = file_get_contents($path); + if ($bytes === false) { + throw new RuntimeException('Source file is unavailable: '.$path); + } + + return $bytes; + } } diff --git a/app/Console/Commands/ImportLegalSourceArchive.php b/app/Console/Commands/ImportLegalSourceArchive.php index 3674c665..e22f3518 100644 --- a/app/Console/Commands/ImportLegalSourceArchive.php +++ b/app/Console/Commands/ImportLegalSourceArchive.php @@ -36,8 +36,7 @@ class ImportLegalSourceArchive extends Command continue; } - $withinApprovedRoot = $realSource && collect($approvedRoots)->contains(fn (string $root) => - str_starts_with(strtolower($realSource), strtolower($root.DIRECTORY_SEPARATOR))); + $withinApprovedRoot = $realSource && collect($approvedRoots)->contains(fn (string $root) => str_starts_with(strtolower($realSource), strtolower($root.DIRECTORY_SEPARATOR))); if (! $realSource || ! $withinApprovedRoot || ! is_file($realSource)) { $this->error($record['external_id'].': archive file missing or outside the approved source archive.'); $failures++; @@ -83,7 +82,7 @@ class ImportLegalSourceArchive extends Command continue; } - $missingEvidence = collect($record['associations'])->filter(fn (array $association) => $association['target_type'] === 'requirement') + $missingEvidence = collect(is_array($record['associations'] ?? null) ? $record['associations'] : [])->filter(fn (array $association) => $association['target_type'] === 'requirement') ->reject(fn (array $association) => DB::table('portal_resource_links')->where('resource_id', $resource->id) ->where('target_type', 'requirement')->where('target_key', $association['target_key'])->exists())->values(); if ($apply && $missingEvidence->isNotEmpty()) { @@ -110,6 +109,7 @@ class ImportLegalSourceArchive extends Command } $this->line($record['external_id'].' | reused/reconciled | document='.$existing->id.' | published='.($resource->published ? 'yes' : 'no')); + continue; } @@ -263,13 +263,13 @@ class ImportLegalSourceArchive extends Command ...array_map(fn (array $item) => ['target_type' => 'requirement', 'target_key' => $item[0], 'relationship' => 'cites', 'applicability' => 'evidence', 'review_status' => 'confirmed', 'condition_text' => 'Citação localizada no texto primário; não confirma interpretação, vigência integral, elegibilidade nem regra executável.', 'basis_reference' => $item[1].' — '.$item[2].' BR n.º 248/2025, Suplemento n.º 1, Lei de 29-12-2025.'], [ - ['fiscal.ice.lei_7_2025', 'Lei n.º 7/2025 (ICE)', 'arts. 1–5 e tabela anexa; pp. 1–2. Art. 1 prorroga as taxas para 2027; art. 5 indica entrada em vigor em 01-01-2026.'], - ['fiscal.customs.lei_8_2025', 'Lei n.º 8/2025 (Pauta Aduaneira)', 'art. 1 altera as Instruções Preliminares, arts. 21(2), 22 e 27; arts. 2–5 e tabela anexa; pp. 2–7.'], - ['fiscal.ispc.lei_9_2025', 'Lei n.º 9/2025 (ISPC)', 'arts. 1–7 e republicação anexa da Lei n.º 5/2009; pp. 8–11.'], - ['fiscal.iva.lei_10_2025', 'Lei n.º 10/2025 (IVA)', 'arts. 1–4; alterações do CIVA identificadas no diploma, incluindo arts. 18, 19, 21, 23, 25-A, 27 e 32–34; pp. 12–15.'], - ['fiscal.irps.lei_11_2025', 'Lei n.º 11/2025 (IRPS)', 'arts. 1–4; alterações do CIRPS, incluindo arts. 21, 22, 48, 50, 54-A, 57, 65 e 65-A; pp. 16–18.'], - ['fiscal.irpc.lei_12_2025', 'Lei n.º 12/2025 (IRPC)', 'arts. 1–4; alterações do CIRPC, incluindo arts. 3, 5, 20, 62, 67, 75 e aditamento do art. 61-A; pp. 19–20.'], - ]), + ['fiscal.ice.lei_7_2025', 'Lei n.º 7/2025 (ICE)', 'arts. 1–5 e tabela anexa; pp. 1–2. Art. 1 prorroga as taxas para 2027; art. 5 indica entrada em vigor em 01-01-2026.'], + ['fiscal.customs.lei_8_2025', 'Lei n.º 8/2025 (Pauta Aduaneira)', 'art. 1 altera as Instruções Preliminares, arts. 21(2), 22 e 27; arts. 2–5 e tabela anexa; pp. 2–7.'], + ['fiscal.ispc.lei_9_2025', 'Lei n.º 9/2025 (ISPC)', 'arts. 1–7 e republicação anexa da Lei n.º 5/2009; pp. 8–11.'], + ['fiscal.iva.lei_10_2025', 'Lei n.º 10/2025 (IVA)', 'arts. 1–4; alterações do CIVA identificadas no diploma, incluindo arts. 18, 19, 21, 23, 25-A, 27 e 32–34; pp. 12–15.'], + ['fiscal.irps.lei_11_2025', 'Lei n.º 11/2025 (IRPS)', 'arts. 1–4; alterações do CIRPS, incluindo arts. 21, 22, 48, 50, 54-A, 57, 65 e 65-A; pp. 16–18.'], + ['fiscal.irpc.lei_12_2025', 'Lei n.º 12/2025 (IRPC)', 'arts. 1–4; alterações do CIRPC, incluindo arts. 3, 5, 20, 62, 67, 75 e aditamento do art. 61-A; pp. 19–20.'], + ]), ], ], [ @@ -310,4 +310,3 @@ class ImportLegalSourceArchive extends Command ]; } } - diff --git a/app/Http/Controllers/InvestmentZoneAttachmentController.php b/app/Http/Controllers/InvestmentZoneAttachmentController.php index e51db2ac..1022f86a 100644 --- a/app/Http/Controllers/InvestmentZoneAttachmentController.php +++ b/app/Http/Controllers/InvestmentZoneAttachmentController.php @@ -95,7 +95,7 @@ class InvestmentZoneAttachmentController extends Controller return DB::transaction(function () use ($request, $id, $action, $data): JsonResponse { $attachment = DB::table('investment_zone_attachments')->where('id', $id)->lockForUpdate()->first(); - abort_unless($attachment, 404); + abort_unless($attachment !== null, 404); abort_if((int) $attachment->version_no !== (int) $data['version_no'], 409, 'O anexo foi alterado. Recarregue antes de continuar.'); $version = (int) $attachment->version_no + 1; $changes = ['version_no' => $version, 'updated_at' => now()]; @@ -137,7 +137,7 @@ class InvestmentZoneAttachmentController extends Controller ->where('a.id', $id)->where('a.zone_id', $zoneId)->where('d.status', 'active')->whereNull('d.deleted_at') ->where('d.malware_scan_status', 'clean')->whereColumn('d.malware_scanned_sha256', 'd.sha256') ->first(['d.file_name', 'd.storage_key', 'd.sha256', 'd.media_type', 'd.malware_scanned_sha256']); - abort_unless($record, 404); + abort_unless($record !== null, 404); return $this->verifiedDownload($zoneId, $record); } @@ -150,7 +150,7 @@ class InvestmentZoneAttachmentController extends Controller ->where('a.review_status', 'reviewed')->where('a.rights_status', 'confirmed')->where('a.personal_data_status', 'none') ->where('d.status', 'active')->whereNull('d.deleted_at')->where('d.malware_scan_status', 'clean') ->whereColumn('d.malware_scanned_sha256', 'd.sha256')->first(['d.file_name', 'd.storage_key', 'd.sha256', 'd.media_type', 'd.malware_scanned_sha256']); - abort_unless($record, 404); + abort_unless($record !== null, 404); $extension = match ($record->media_type) { 'application/pdf' => 'pdf', 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', default => 'bin' @@ -159,7 +159,7 @@ class InvestmentZoneAttachmentController extends Controller return $this->verifiedDownload($zoneId, $record, str_starts_with($record->media_type, 'image/'), 'zone-attachment-'.$id.'.'.$extension); } - private function verifiedDownload(string $zoneId, object $document, bool $inline = false, ?string $downloadName = null): BinaryFileResponse + private function verifiedDownload(string $zoneId, \stdClass $document, bool $inline = false, ?string $downloadName = null): BinaryFileResponse { $root = realpath(Storage::disk('local')->path('investment-zones/'.$zoneId)); $path = realpath(Storage::disk('local')->path($document->storage_key)); @@ -195,6 +195,7 @@ class InvestmentZoneAttachmentController extends Controller abort_unless($expected !== null && $image[2] === $expected, 422, 'O conteúdo da imagem não corresponde ao formato declarado.'); } + /** @param array $metadata */ private function event(string $attachmentId, int $actor, string $action, int $version, array $metadata = []): void { DB::table('investment_zone_attachment_events')->insert([ diff --git a/app/Http/Controllers/InvestmentZoneController.php b/app/Http/Controllers/InvestmentZoneController.php index 48b4e02f..36532e7e 100644 --- a/app/Http/Controllers/InvestmentZoneController.php +++ b/app/Http/Controllers/InvestmentZoneController.php @@ -17,6 +17,7 @@ use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; class InvestmentZoneController extends Controller { @@ -92,7 +93,7 @@ class InvestmentZoneController extends Controller $publicColumns = array_values(array_filter($publicColumns, fn ($column) => Schema::hasColumn('investment_zones', $column))); $rows = $zones->search($filters, $management)->orderBy('name')->orderBy('id') ->paginate(12, $management ? ['*'] : $publicColumns)->withQueryString(); - $rows->getCollection()->transform(function ($zone) use ($management) { + $rows->getCollection()->transform(function (InvestmentZone $zone) use ($management) { $territoryPath = app(InvestmentGeography::class)->path($zone->primary_territory_id, $management); $zone->setAttribute('province', collect($territoryPath)->firstWhere('location_type', 'province')->name ?? $zone->province); $zone->setAttribute('district', collect($territoryPath)->firstWhere('location_type', 'district')->name ?? $zone->district); @@ -110,7 +111,7 @@ class InvestmentZoneController extends Controller return $request->is('api/*') ? response()->json($props) : Inertia::render('investment/zones', $props); } - public function export(Request $request, InvestmentZones $zones) + public function export(Request $request, InvestmentZones $zones): StreamedResponse { $filters = $request->validate(['category' => ['nullable', 'in:zee,zfi,industrial_park'], 'physical_category' => ['nullable', 'in:economic_zone,industrial_park,agro_industrial_park,industrial_logistics_park,industrial_free_zone,integrated_tourism_area,other,unknown'], 'legal_regime' => ['nullable', 'in:zee,zfi,general,pending,not_applicable'], 'development_status' => ['nullable', 'in:announced,planned,in_development,partially_operational,operational,suspended,unknown'], @@ -123,6 +124,9 @@ class InvestmentZoneController extends Controller return response()->streamDownload(function () use ($query): void { $geography = app(InvestmentGeography::class); $stream = fopen('php://output', 'wb'); + if ($stream === false) { + throw new \RuntimeException('CSV output stream unavailable.'); + } fwrite($stream, "\xEF\xBB\xBF"); fputcsv($stream, ['Código', 'Nome', 'Categoria física', 'Regime', 'Desenvolvimento', 'Disponibilidade', 'Província', 'Distrito', 'Área declarada', 'Unidade', 'Fontes'], ';'); $query->chunk(200, function ($zones) use ($stream, $geography): void { diff --git a/app/Http/Controllers/InvestorGuidanceController.php b/app/Http/Controllers/InvestorGuidanceController.php new file mode 100644 index 00000000..c1149d49 --- /dev/null +++ b/app/Http/Controllers/InvestorGuidanceController.php @@ -0,0 +1,74 @@ +user(); + $organizations = DB::table('memberships')->where('user_id', $actor->id)->whereNull('deleted_at')->pluck('organization_id'); + $cases = DB::table('desk_cases')->whereNull('closed_at')->where(fn ($q) => $q->where(fn ($p) => $p->where('user_id', $actor->id)->whereNull('organization_id'))->orWhereIn('organization_id', $organizations)) + ->orderByDesc('updated_at')->get(['id', 'title']); + + return response()->json(['cases' => $cases]); + } + + public function show(Request $request, string $id, InvestorGuidance $guidance): JsonResponse + { + return response()->json($guidance->view($request->user(), $id)); + } + + public function assumptions(Request $request, string $id, InvestorGuidance $guidance): JsonResponse|RedirectResponse + { + $guidance->assumptions($request->user(), $id, $request->all()); + + return $this->completed($request, 'Pressupostos corrigidos; os dados dos módulos de origem foram preservados.'); + } + + public function feedback(Request $request, string $id, InvestorGuidance $guidance): JsonResponse|RedirectResponse + { + $guidance->feedback($request->user(), $id, $request->all()); + + return $this->completed($request, 'Revisão registada. Assumir uma tarefa continua a ser uma acção separada.'); + } + + public function task(Request $request, string $id, InvestorGuidance $guidance): JsonResponse|RedirectResponse + { + $task = $guidance->acceptTask($request->user(), $id, $request->all()); + + return $request->expectsJson() ? response()->json(['id' => $task->id], 201) : back()->with('success', 'Tarefa assumida no CRM interno. Sincronização externa pendente de finalidade e autorização.'); + } + + public function updateTask(Request $request, string $id, string $task, InvestorGuidance $guidance): JsonResponse|RedirectResponse + { + $guidance->updateTask($request->user(), $id, $task, $request->all()); + + return $this->completed($request, 'Resultado da tarefa registado no mesmo histórico do dossier.'); + } + + public function search(Request $request, string $id, VirtualDesk $desk, EvidenceRepository $evidence): JsonResponse + { + $desk->case($request->user(), $id); + $data = $request->validate(['query' => 'required|string|min:3|max:300']); + $enabled = config('investmoz_ai.enabled') && Schema::hasTable('ai_controls') && DB::table('ai_controls')->where('capability', 'search')->where('suspended', false)->exists(); + $items = $enabled ? $evidence->search($data['query'], 'pt', ['public_only' => true]) : []; + + return response()->json(['mode' => 'lexical_evidence', 'model' => null, 'status' => ! $enabled ? 'unavailable' : ($items ? 'evidence_found' : 'no_evidence'), + 'consulted' => $items, 'limitations' => ['Apenas excertos das fontes públicas autorizadas indexadas; não é uma consulta exaustiva nem validação jurídica.']]); + } + + private function completed(Request $request, string $message): JsonResponse|RedirectResponse + { + return $request->expectsJson() ? response()->json(['message' => $message]) : back()->with('success', $message); + } +} diff --git a/app/Http/Controllers/LandAssistanceController.php b/app/Http/Controllers/LandAssistanceController.php index 58599720..f215aa8e 100644 --- a/app/Http/Controllers/LandAssistanceController.php +++ b/app/Http/Controllers/LandAssistanceController.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace App\Http\Controllers; -use App\Jobs\ScanDocumentForMalware; use App\Http\Requests\StoreLandAssistanceCaseRequest; +use App\Jobs\ScanDocumentForMalware; use App\Models\InterestRequest; use App\Models\LandAssistanceCase; use App\Services\InterestSubmission; diff --git a/app/Http/Controllers/PortalServicesController.php b/app/Http/Controllers/PortalServicesController.php index 6369d91b..47e54927 100644 --- a/app/Http/Controllers/PortalServicesController.php +++ b/app/Http/Controllers/PortalServicesController.php @@ -8,10 +8,11 @@ use App\Models\Sector; use App\Services\IdempotencyConflict; use App\Services\InterestSubmission; use App\Services\Malware\MalwareScanner; +use App\Services\VirtualDesk; use Illuminate\Database\Query\Builder; use Illuminate\Database\QueryException; -use Illuminate\Http\RedirectResponse; use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -47,7 +48,7 @@ class PortalServicesController extends Controller ->where('d.malware_scan_status', 'clean')->whereColumn('d.malware_scanned_sha256', 'd.sha256'); } - public function page(Request $request): Response|JsonResponse + public function page(Request $request): Response|JsonResponse|RedirectResponse { $page = $request->route()->defaults['page']; if ($page === 'partnerships' && Schema::hasColumn('portal_partnerships', 'partnership_type')) { @@ -105,12 +106,12 @@ class PortalServicesController extends Controller } $pageResult = $query->orderByRaw("CASE WHEN r.document_type IN ('official_form','digital_adaptation','guidance_template') THEN 1 ELSE 0 END") ->orderByDesc('r.essential')->orderBy('r.title')->select([ - 'r.id', 'r.document_id', 'r.title', 'r.description', 'r.category', 'r.topic', 'r.document_type', 'r.document_number', 'r.document_date', - 'r.applicable_from', 'r.applicable_until', - 'r.source_url', 'r.language', 'r.version_label', 'r.document_group_id', 'r.territorial_scope', 'r.territory_id', - 'r.source_confirmation', 'r.legal_status', 'r.rights_status', 'r.last_reviewed_at', 'r.last_reviewed_by', 'r.independent_review', 'r.metadata_version', 'r.source_reference', 'r.essential', - 'd.file_name', 'd.media_type', 'd.size_bytes', 'd.issuer', 'd.issued_on', 'd.valid_until', 'd.version_no', - ])->paginate(20)->withQueryString(); + 'r.id', 'r.document_id', 'r.title', 'r.description', 'r.category', 'r.topic', 'r.document_type', 'r.document_number', 'r.document_date', + 'r.applicable_from', 'r.applicable_until', + 'r.source_url', 'r.language', 'r.version_label', 'r.document_group_id', 'r.territorial_scope', 'r.territory_id', + 'r.source_confirmation', 'r.legal_status', 'r.rights_status', 'r.last_reviewed_at', 'r.last_reviewed_by', 'r.independent_review', 'r.metadata_version', 'r.source_reference', 'r.essential', + 'd.file_name', 'd.media_type', 'd.size_bytes', 'd.issuer', 'd.issued_on', 'd.valid_until', 'd.version_no', + ])->paginate(20)->withQueryString(); $pageResult->getCollection()->transform(function ($resource) { $resource->links = DB::table('portal_resource_links')->where('resource_id', $resource->id) ->orderBy('target_type')->orderBy('target_key')->get($this->resourceLinkColumns()); @@ -153,6 +154,9 @@ class PortalServicesController extends Controller $props['partnerships'] = $query->orderByDesc('id')->select(['id', 'title', 'sector', 'province', 'offer', 'seeking'])->paginate(20)->withQueryString(); } if ($page === 'desk') { + if (app(VirtualDesk::class)->installed()) { + return redirect('/balcao-virtual/painel'); + } $props['requests'] = InterestRequest::where('metadata->portal_user_id', $request->user()->id)->latest()->limit(100)->get(['reference', 'status', 'created_at', 'metadata']); $props['myPartnerships'] = DB::table('portal_partnerships')->where('user_id', $request->user()->id)->latest()->limit(100)->get(['id', 'title', 'status']); } @@ -161,7 +165,7 @@ class PortalServicesController extends Controller return Inertia::render('portal/services', $props); } - public function recommendations(Request $request) + public function recommendations(Request $request): JsonResponse { $data = $request->validate([ 'activity_id' => ['nullable', 'string', 'max:180'], 'province_id' => ['nullable', 'uuid'], @@ -199,10 +203,11 @@ class PortalServicesController extends Controller ? DB::table('portal_resource_links')->where('resource_id', $resource->id)->where('target_type', 'requirement') ->where('review_status', 'confirmed')->orderBy('target_key')->get(['target_key', 'basis_reference', 'review_status']) : collect(); + return [...(array) $resource, 'download_url' => route('services.download', ['id' => $resource->id]), - 'recommendation_reason' => $matched?->condition_text ?? ('Relacionado com '.$matched?->target_type.' '.$matched?->target_key.'; confirme a aplicabilidade ao seu projecto.'), - 'applicability' => $matched?->applicability ?? 'support', + 'recommendation_reason' => $matched->condition_text ?? ('Relacionado com '.$matched?->target_type.' '.$matched?->target_key.'; confirme a aplicabilidade ao seu projecto.'), + 'applicability' => $matched->applicability ?? 'support', 'basis_reference' => $matched?->basis_reference, 'documentary_evidence' => $evidence->map(fn ($link) => ['key' => $link->target_key, 'label' => config('document-requirements.catalogue.'.$link->target_key.'.label', $link->target_key), @@ -357,7 +362,7 @@ class PortalServicesController extends Controller return Inertia::render('portal/manage', ['resources' => $resources, 'requirementCatalogue' => $this->requirementEvidenceEnabled() - ? collect(config('document-requirements.catalogue'))->map(fn ($item, $key) => ['key' => $key, ...$item])->values() + ? collect(is_array($catalogue = config('document-requirements.catalogue', [])) ? $catalogue : [])->map(fn ($item, $key) => ['key' => $key, ...$item])->values() : collect(), 'requirementAssociationsEnabled' => $this->requirementEvidenceEnabled(), 'partnerships' => DB::table('portal_partnerships')->latest()->limit(100)->get(), @@ -548,10 +553,12 @@ class PortalServicesController extends Controller ]); if ($resource === null) { $skipped[$resourceId] = 'Registo ou ficheiro inexistente.'; + continue; } if ($resource->published && $resource->data_classification === 'public' && $resource->resource_rights === 'authorized' && $resource->file_rights === 'authorized') { $published[$resourceId] = 'Já publicado.'; + continue; } $path = Storage::disk('local')->path($resource->storage_key); @@ -565,12 +572,14 @@ class PortalServicesController extends Controller || ! hash_equals((string) $resource->sha256, $actualHash) || ! hash_equals((string) $resource->sha256, (string) $resource->malware_scanned_sha256)) { $skipped[$resourceId] = 'Ficheiro indisponível, não limpo ou diferente dos bytes analisados.'; + continue; } if (in_array($resource->resource_rights, ['restricted'], true) || in_array($resource->file_rights, ['restricted'], true) || $resource->source_confirmation === 'unconfirmed' || ! filter_var($resource->source_url, FILTER_VALIDATE_URL) || parse_url($resource->source_url, PHP_URL_SCHEME) !== 'https') { $skipped[$resourceId] = 'Restrição de divulgação ou origem identificada em falta.'; + continue; } @@ -600,6 +609,7 @@ class PortalServicesController extends Controller ->with('disclosureResults', ['published' => $published, 'skipped' => $skipped]); } + /** @param array $additional */ private function audit(Request $request, string $action, string $table, int $id, array $additional = []): void { DB::table('audit_events')->insert(['source_system' => 'portal-services', 'entity_type' => $table, @@ -641,6 +651,7 @@ class PortalServicesController extends Controller } } + /** @return array */ private function validateResource(Request $request): array { $data = $this->validateManagement($request, [ @@ -691,6 +702,9 @@ class PortalServicesController extends Controller /** Keep validation redirects on the management GET page even if the request * arrived with a stale Referer pointing at a write-only resource endpoint. + * + * @param array $rules + * @return array */ private function validateManagement(Request $request, array $rules): array { @@ -701,6 +715,7 @@ class PortalServicesController extends Controller } } + /** @param array> $errors */ private function managementValidationException(array $errors): ValidationException { $exception = ValidationException::withMessages($errors); @@ -709,6 +724,8 @@ class PortalServicesController extends Controller return $exception; } + /** @param array $data + * @return array */ private function resourceFields(array $data, int $actor, ?string $identity, ?int $duplicate, bool $creating): array { unset($data['associations'], $data['issuer'], $data['data_classification']); @@ -725,6 +742,7 @@ class PortalServicesController extends Controller return $data; } + /** @param list> $links */ private function syncResourceLinks(int $resourceId, array $links): void { foreach ($links as $link) { @@ -741,7 +759,8 @@ class PortalServicesController extends Controller } } - private function assertPublicClassificationEligible(array $data, object $document): void + /** @param array $data */ + private function assertPublicClassificationEligible(array $data, \stdClass $document): void { if (($data['data_classification'] ?? $document->data_classification) !== 'public') { return; @@ -756,6 +775,7 @@ class PortalServicesController extends Controller 'Identifique e confirme a fonte antes de classificar o ficheiro como público.'); } + /** @param array $data */ private function identityKey(array $data): ?string { if (empty($data['issuer']) || empty($data['document_number'])) { @@ -782,6 +802,7 @@ class PortalServicesController extends Controller && DB::table('migrations')->where('migration', '2026_10_06_200000_add_document_requirement_evidence_links')->exists(); } + /** @return list */ private function resourceLinkColumns(): array { $columns = ['target_type', 'target_key', 'relationship', 'applicability', 'condition_text', 'basis_reference']; @@ -792,9 +813,10 @@ class PortalServicesController extends Controller return $columns; } + /** @return list */ private function territoryScopeIds(string $id): array { - return collect(DB::select(<<<'SQL' + $rows = DB::select(<<<'SQL' WITH RECURSIVE territory_tree AS ( SELECT id FROM geo_locations WHERE id = ? AND deleted_at IS NULL AND data_classification = 'public' UNION ALL @@ -803,6 +825,15 @@ WITH RECURSIVE territory_tree AS ( WHERE child.deleted_at IS NULL AND child.data_classification = 'public' ) SELECT id FROM territory_tree -SQL, [$id]))->pluck('id')->all() ?: [$id]; +SQL, [$id]); + $ids = []; + foreach ($rows as $row) { + if (! is_string($row->id)) { + throw new \RuntimeException('Territory identifier must be a string.'); + } + $ids[] = $row->id; + } + + return $ids ?: [$id]; } } diff --git a/app/Http/Controllers/VirtualDeskController.php b/app/Http/Controllers/VirtualDeskController.php index 091d9732..a2f3ac74 100644 --- a/app/Http/Controllers/VirtualDeskController.php +++ b/app/Http/Controllers/VirtualDeskController.php @@ -4,29 +4,41 @@ namespace App\Http\Controllers; use App\Jobs\ScanDocumentForMalware; use App\Models\User; +use App\Services\Ai\InvestorGuidance; +use App\Services\InstitutionalLinkMonitor; +use App\Services\InvestmentGeography; use App\Services\VirtualDesk; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Arr; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Inertia\Inertia; +use Inertia\Response; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; class VirtualDeskController extends Controller { - public function catalogue(Request $request, VirtualDesk $desk) + public function catalogue(Request $request, VirtualDesk $desk): JsonResponse|Response { $filters = $request->validate(['q' => 'nullable|string|max:120', 'theme' => 'nullable|string|max:80', 'profile' => 'nullable|string|max:100']); $installed = $desk->installed(); $services = $installed ? DB::table('desk_service_versions')->where('status', 'published')->orderBy('code')->orderByDesc('version')->get()->unique('code')->map(fn ($s) => ['id' => $s->id, 'version' => $s->version, 'definition' => json_decode($s->definition, true), 'source' => json_decode($s->source, true)])->values() : collect(); - $services = $services->filter(fn ($s) => (! ($filters['q'] ?? '') || str_contains(mb_strtolower(json_encode($s['definition'], JSON_UNESCAPED_UNICODE)), mb_strtolower($filters['q']))) + $services = $services->filter(fn ($s) => (! ($filters['q'] ?? '') || str_contains(mb_strtolower(json_encode($s['definition'], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)), mb_strtolower($filters['q']))) && (! ($filters['theme'] ?? '') || $s['definition']['theme'] === $filters['theme']) && (! ($filters['profile'] ?? '') || $s['definition']['profile'] === $filters['profile']))->values(); $page = max(1, (int) $request->query('page', 1)); $data = ['installed' => $installed, 'services' => $services->slice(($page - 1) * 12, 12)->values(), 'total' => $services->count(), 'page' => $page, 'filters' => $filters, 'prepared' => $installed ? [] : config('virtual-desk.services'), 'source' => config('virtual-desk.source'), 'authenticated' => (bool) $request->user(), 'support' => config('portal.support')]; + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/catalogue', $data); } - public function panel(Request $request, VirtualDesk $desk) + public function panel(Request $request, VirtualDesk $desk): JsonResponse|Response { $actor = $request->user(); $organizations = DB::table('memberships')->where('user_id', $actor->id)->whereNull('deleted_at')->pluck('organization_id'); @@ -34,188 +46,269 @@ class VirtualDeskController extends Controller $queue = DB::table('desk_requests as r')->join('desk_cases as c', 'c.id', '=', 'r.case_id')->whereNotIn('r.status', ['draft', 'cancelled'])->when(! $actor->can('manage-portal'), fn ($q) => $q->where('r.analyst_id', $actor->id)) ->orderByRaw('r.due_at ASC NULLS LAST')->get(['r.id', 'r.protocol', 'r.status', 'r.version', 'r.analyst_id', 'r.due_at', 'c.title']); $data = ['cases' => $cases, 'queue' => $queue, 'notifications' => DB::table('desk_notifications')->where('user_id', $actor->id)->orderByDesc('id')->limit(100)->get(), - 'legacyRequests' => DB::table('interest_requests')->where('metadata->portal_user_id', $actor->id)->orderByDesc('created_at')->get(['reference', 'status', 'created_at']), + 'legacyRequests' => DB::table('interest_requests')->where('metadata->portal_user_id', $actor->id)->whereNull('deleted_at')->orderByDesc('created_at')->get(['id', 'reference', 'status', 'created_at']), 'companies' => DB::table('investment_company_declarations')->where('user_id', $actor->id)->get(['id', 'payload'])->map(fn ($c) => ['id' => $c->id, 'name' => $desk->decode($c->payload)['name'] ?? 'Declaração empresarial']), 'submissions' => DB::table('investment_submissions')->where('user_id', $actor->id)->get(['id', 'status']), + 'journeys' => DB::table('investor_journeys')->where('user_id', $actor->id)->whereNull('deleted_at')->get(['id', 'dossier_id', 'status']), + 'simulationDrafts' => DB::table('simulation_form_drafts')->where('owner_user_id', $actor->id)->whereNull('deleted_at')->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))->get(['id', 'version_no']), + 'guidanceTasks' => app(InvestorGuidance::class)->installed() ? DB::table('tasks as t')->join('desk_cases as c', 'c.id', '=', 't.desk_case_id') + ->whereNull('t.deleted_at')->when(! $actor->can('manage-portal'), fn ($q) => $q->where(fn ($p) => $p->where('t.assignee_user_id', $actor->id)->orWhere(fn ($s) => $s->where('c.user_id', $actor->id)->whereNull('c.organization_id'))->orWhereIn('c.organization_id', $organizations))) + ->orderByRaw('t.due_at ASC NULLS LAST')->get(['t.id', 't.title', 't.status', 't.due_at', 'c.id as case_id', 'c.title as case_title']) : [], 'organizations' => DB::table('organizations')->whereIn('id', $organizations)->get(['id', 'name']), 'manager' => $actor->can('manage-portal'), 'states' => config('virtual-desk.request_states')]; + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/panel', $data); } - public function createCase(Request $request, VirtualDesk $desk) + public function createCase(Request $request, VirtualDesk $desk): JsonResponse|RedirectResponse { $case = $desk->createCase($request->user(), $request->all()); - return $request->expectsJson() ? response()->json(['id' => $case->id, 'version' => $case->version], 201) : redirect('/balcao-virtual/dossiers/'.$case->id); + + return $request->expectsJson() ? response()->json(['id' => $case->id, 'version' => $case->version], 201) : redirect('/balcao-virtual/dossiers/'.$case->id) + ->with('success', 'Dossier guardado e disponível para continuar. Próxima ação: escolha um serviço publicado e prepare o rascunho de apoio. Nenhum pedido foi submetido.'); } - public function showCase(Request $request, string $id, VirtualDesk $desk) + public function showCase(Request $request, string $id, VirtualDesk $desk): JsonResponse|Response { - $case = $desk->case($request->user(), $id); $owner = $desk->owns($request->user(), $case, false); + $case = $desk->case($request->user(), $id); + $owner = $desk->owns($request->user(), $case, false); $this->auditRead($request, 'desk_cases', $id); - $data = ['case' => [...(array) $case, 'payload' => \Illuminate\Support\Arr::except($desk->decode($case->payload), ['_creation_hash']), 'workflow' => json_decode($case->workflow, true)], + $data = ['case' => [...(array) $case, 'payload' => Arr::except($desk->decode($case->payload), $owner ? ['_creation_hash'] : ['_creation_hash', 'profile', 'company', 'investment', 'name', 'email']), 'workflow' => json_decode($case->workflow, true)], + 'legacyRequest' => ($case->interest_request_id ?? null) ? DB::table('interest_requests as r')->leftJoin('users as u', 'u.id', '=', 'r.assigned_to')->where('r.id', $case->interest_request_id)->whereNull('r.deleted_at') + ->when(! $request->user()->can('manage-portal'), fn ($q) => $q->where('r.metadata->portal_user_id', $request->user()->id)) + ->first(['r.id', 'r.reference', 'r.status', 'r.next_action', 'r.next_action_due_at', 'u.name as responsible_name']) : null, 'owner' => $owner, 'canWrite' => $desk->owns($request->user(), $case), 'manager' => $request->user()->can('manage-portal'), 'requests' => DB::table('desk_requests')->where('case_id', $id)->when(! $owner && ! $request->user()->can('manage-portal'), fn ($q) => $q->where('analyst_id', $request->user()->id))->orderByDesc('updated_at')->get(['id', 'status', 'protocol', 'service_version_id', 'version']), 'services' => DB::table('desk_service_versions')->where('status', 'published')->orderByDesc('version')->get(['id', 'code', 'version', 'definition'])->unique('code')->values()->map(fn ($s) => ['id' => $s->id, 'title' => json_decode($s->definition, true)['title'], 'version' => $s->version]), - 'documents' => $this->documents($id), 'events' => $this->events($id, $desk), 'stepStates' => config('virtual-desk.step_states'), 'states' => config('virtual-desk.request_states'), - 'links' => DB::table('desk_institutional_links')->where('status', 'active')->whereNotNull('verified_at')->get(['id', 'stage', 'entity', 'purpose', 'contact', 'verified_at']), 'source' => config('virtual-desk.source')]; + 'documents' => $this->documents($id, $request->user()), 'events' => $this->events($id, $desk, $request->user()), 'stepStates' => config('virtual-desk.step_states'), 'states' => config('virtual-desk.request_states'), + 'links' => DB::table('desk_institutional_links')->get()->map(fn ($link) => [...(array) $link, 'monitor' => app(InstitutionalLinkMonitor::class)->status($link)]), 'source' => config('virtual-desk.source'), 'support' => config('portal.support'), + 'guidance' => app(InvestorGuidance::class)->installed() ? app(InvestorGuidance::class)->view($request->user(), $id) : null]; + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/case', $data); } - private function documents(string $case) + /** @return Collection */ + private function documents(string $case, User $actor): Collection { return DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $case)->whereNull('d.deleted_at') + ->when(! app(VirtualDesk::class)->owns($actor, app(VirtualDesk::class)->case($actor, $case), false) && ! $actor->can('manage-portal'), fn ($q) => $q->where(fn ($p) => $p->whereNull('l.request_id')->orWhereIn('l.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id')))) ->get(['l.id', 'l.document_id', 'l.request_id', 'l.category', 'l.stage', 'l.source_reference', 'l.valid_until', 'd.file_name', 'd.status', 'd.malware_scan_status', 'd.sha256']); } - private function events(string $case, VirtualDesk $desk) + /** @return Collection}> */ + private function events(string $case, VirtualDesk $desk, User $actor): Collection { - return DB::table('desk_events as e')->join('users as u', 'u.id', '=', 'e.actor_id')->where('e.case_id', $case)->orderBy('e.created_at')->get(['e.id', 'e.request_id', 'e.action', 'e.payload', 'e.version', 'e.created_at', 'u.name'])->map(fn ($e) => [...(array) $e, 'payload' => $desk->decode($e->payload)]); + return DB::table('desk_events as e')->join('users as u', 'u.id', '=', 'e.actor_id')->where('e.case_id', $case) + ->when(! $desk->owns($actor, $desk->case($actor, $case), false) && ! $actor->can('manage-portal'), fn ($q) => $q->where(fn ($p) => $p->whereNull('e.request_id')->orWhereIn('e.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id')))) + ->orderBy('e.event_order')->get(['e.id', 'e.request_id', 'e.action', 'e.payload', 'e.version', 'e.created_at', 'u.name'])->map(fn ($e) => ['id' => $e->id, 'request_id' => $e->request_id, 'action' => $e->action, 'version' => $e->version, 'created_at' => $e->created_at, 'name' => $e->name, 'payload' => $desk->decode($e->payload)]); } - public function stage(Request $request, string $id, VirtualDesk $desk) + public function stage(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $case = $desk->stage($request->user(), $id, $request->all()); + return $request->expectsJson() ? response()->json(['version' => $case->version]) : back()->with('success', 'Etapa e evidência registadas.'); } - public function createRequest(Request $request, string $id, VirtualDesk $desk) + public function createRequest(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $data = $request->validate(['service_version_id' => 'required|uuid', 'client_key' => 'required|uuid']); $row = $desk->createRequest($request->user(), $id, $data['service_version_id'], $data['client_key']); + return $request->expectsJson() ? response()->json(['id' => $row->id, 'version' => $row->version], 201) : redirect('/balcao-virtual/pedidos/'.$row->id); } - public function showRequest(Request $request, string $id, VirtualDesk $desk) + public function showRequest(Request $request, string $id, VirtualDesk $desk): JsonResponse|Response { - $row = $desk->request($request->user(), $id); $case = $desk->case($request->user(), $row->case_id); $payload = $desk->decode($row->payload); + $row = $desk->request($request->user(), $id); + $case = $desk->case($request->user(), $row->case_id); + $payload = $desk->decode($row->payload); + $messageKinds = []; + foreach (DB::table('desk_events')->where('request_id', $id)->where('action', 'message_added')->get() as $event) { + $original = $desk->decode($event->payload); + if (isset($original['message_id'], $original['kind'])) { + $messageKinds[$original['message_id']] = $original['kind']; + } + } $this->auditRead($request, 'desk_requests', $id); $data = ['request' => [...(array) $row, 'payload' => $payload, 'submitted_snapshot' => $desk->decode($row->submitted_snapshot), 'editable_fields' => json_decode($row->editable_fields ?? '[]', true)], - 'case' => ['id' => $case->id, 'title' => $case->title], 'definition' => $desk->definition($row), + 'case' => ['id' => $case->id, 'title' => $case->title], 'caseVersion' => $case->version, 'definition' => $desk->definition($row), 'serviceSource' => json_decode(DB::table('desk_service_versions')->where('id', $row->service_version_id)->value('source'), true), 'owner' => $desk->owns($request->user(), $case), 'manager' => $request->user()->can('manage-portal'), 'analyst' => (int) $row->analyst_id === $request->user()->id, 'fieldsEditable' => in_array($row->status, ['draft', 'returned'], true), 'states' => config('virtual-desk.request_states'), 'transitions' => config('virtual-desk.transitions.'.$row->status, []), - 'findings' => $desk->findings($payload), 'ai' => config('virtual-desk.ai'), 'documents' => $this->documents($case->id)->filter(fn ($d) => ! $d->request_id || $d->request_id === $id)->values(), - 'messages' => DB::table('desk_messages as m')->join('users as u', 'u.id', '=', 'm.actor_id')->where('request_id', $id)->orderBy('m.created_at')->get(['m.id', 'm.body', 'm.created_at', 'u.name'])->map(fn ($m) => [...(array) $m, 'body' => \Illuminate\Support\Facades\Crypt::decryptString($m->body)]), - 'events' => $this->events($case->id, $desk)->filter(fn ($e) => $e['request_id'] === $id)->values(), + 'findings' => $desk->findings($payload), 'ai' => config('virtual-desk.ai'), 'documents' => $this->documents($case->id, $request->user())->filter(fn ($d) => ! $d->request_id || $d->request_id === $id)->values(), + 'messages' => DB::table('desk_messages as m')->join('users as u', 'u.id', '=', 'm.actor_id')->where('request_id', $id)->orderBy('m.created_at')->get(['m.id', 'm.actor_id', 'm.body', 'm.created_at', 'u.name'])->map(fn ($m) => [...(array) $m, 'body' => Crypt::decryptString($m->body), + 'kind' => $messageKinds[$m->id] ?? 'historical_unclassified']), + 'events' => $this->events($case->id, $desk, $request->user())->filter(fn ($e) => $e['request_id'] === $id)->values(), 'revisions' => DB::table('desk_revisions')->where('request_id', $id)->orderByDesc('version')->get(['version', 'snapshot', 'created_at'])->map(fn ($r) => [...(array) $r, 'snapshot' => $desk->decode($r->snapshot)]), 'payments' => DB::table('desk_payments')->where('request_id', $id)->get(), 'analysts' => $request->user()->can('manage-portal') ? User::orderBy('name')->get(['id', 'name']) : [], - 'territories' => app(\App\Services\InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name', 'location_type'])]; + 'territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name', 'location_type'])]; + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/request', $data); } - public function save(Request $request, string $id, VirtualDesk $desk) + public function save(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $row = $desk->save($request->user(), $id, $request->all(), $request->boolean('submit')); + return $request->expectsJson() ? response()->json(['id' => $row->id, 'version' => $row->version, 'status' => $row->status, 'protocol' => $row->protocol]) : back()->with('success', $request->boolean('submit') ? 'Pedido submetido; protocolo disponível.' : 'Rascunho guardado.'); } - public function transition(Request $request, string $id, VirtualDesk $desk) + public function transition(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $row = $desk->transition($request->user(), $id, $request->all()); + return $request->expectsJson() ? response()->json(['version' => $row->version, 'status' => $row->status]) : back()->with('success', 'Decisão interna registada.'); } - public function assign(Request $request, string $id, VirtualDesk $desk) + public function assign(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { abort_unless($request->user()->can('manage-portal'), 403); $data = $request->validate(['version' => 'required|integer', 'analyst_id' => 'required|integer|exists:users,id', 'reason' => 'required|string|min:3|max:3000', 'due_at' => 'nullable|date']); DB::transaction(function () use ($request, $id, $desk, $data) { - $row = $desk->request($request->user(), $id, false, true); $case = $desk->case($request->user(), $row->case_id); + $row = $desk->request($request->user(), $id, false, true); + $case = $desk->case($request->user(), $row->case_id); abort_unless((int) $row->version === (int) $data['version'] && ! in_array($row->status, ['draft', 'cancelled', 'closed'], true), 409); - $analyst = User::findOrFail($data['analyst_id']); abort_if($desk->owns($analyst, $case, false), 422, 'O proponente não pode ser o analista.'); + $analyst = User::where('id', $data['analyst_id'])->firstOrFail(); + abort_unless($analyst->hasVerifiedEmail(), 422, 'O responsável deve ter conta verificada.'); + abort_if($desk->owns($analyst, $case, false), 422, 'O proponente não pode ser o analista.'); DB::table('desk_requests')->where('id', $id)->update(['analyst_id' => $analyst->id, 'due_at' => $data['due_at'] ?? null, 'version' => $row->version + 1, 'updated_at' => now()]); $desk->event($case, $request->user(), 'analyst_assigned', [...$data, 'deadline_basis' => 'internal_target_not_legal_deadline'], DB::table('desk_requests')->where('id', $id)->first()); }); + return $request->expectsJson() ? response()->json(['ok' => true]) : back()->with('success', 'Analista e meta interna atribuídos.'); } - public function message(Request $request, string $id, VirtualDesk $desk) + public function message(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $data = $request->validate(['body' => 'required|string|min:1|max:10000', 'client_key' => 'required|uuid']); DB::transaction(function () use ($request, $id, $desk, $data) { - $row = $desk->request($request->user(), $id, false, true); $case = $desk->case($request->user(), $row->case_id); + $row = $desk->request($request->user(), $id, false, true); + $case = $desk->case($request->user(), $row->case_id); abort_if(in_array($row->status, ['closed', 'cancelled'], true), 409, 'Pedido encerrado.'); + abort_unless($desk->owns($request->user(), $case) || $request->user()->can('manage-portal') || (int) $row->analyst_id === $request->user()->id, 403); $existing = DB::table('desk_messages')->where('request_id', $id)->where('actor_id', $request->user()->id)->where('client_key', $data['client_key'])->first(); - if ($existing) { abort_unless(\Illuminate\Support\Facades\Crypt::decryptString($existing->body) === $data['body'], 409); return; } - DB::table('desk_messages')->insert(['id' => (string) Str::uuid(), 'request_id' => $id, 'actor_id' => $request->user()->id, 'client_key' => $data['client_key'], 'body' => \Illuminate\Support\Facades\Crypt::encryptString($data['body'])]); - $desk->event($case, $request->user(), 'message_added', [], $row); + if ($existing) { + abort_unless(Crypt::decryptString($existing->body) === $data['body'], 409); + + return; + } + $messageId = (string) Str::uuid(); + DB::table('desk_messages')->insert(['id' => $messageId, 'request_id' => $id, 'actor_id' => $request->user()->id, 'client_key' => $data['client_key'], 'body' => Crypt::encryptString($data['body'])]); + $desk->event($case, $request->user(), 'message_added', ['message_id' => $messageId, + 'kind' => $desk->owns($request->user(), $case) ? 'investor_message' : 'responsible_response'], $row); }); + return $request->expectsJson() ? response()->json(['ok' => true]) : back()->with('success', 'Mensagem privada guardada.'); } - public function receipt(Request $request, string $id, VirtualDesk $desk) + public function receipt(Request $request, string $id, VirtualDesk $desk): StreamedResponse { - $row = $desk->request($request->user(), $id); abort_unless($row->protocol && $row->submitted_snapshot, 404); + $row = $desk->request($request->user(), $id); + abort_unless($row->protocol && $row->submitted_snapshot, 404); $snapshot = $desk->decode($row->submitted_snapshot); $this->auditRead($request, 'desk_requests', $id, 'receipt_downloaded'); + return response()->streamDownload(function () use ($row, $snapshot) { echo 'Balcão Virtual — recibo de submissão interna'.PHP_EOL.$row->protocol.PHP_EOL.'Data: '.$row->submitted_at.PHP_EOL.'Versão: '.$row->version.PHP_EOL; echo json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); }, $row->protocol.'.txt', ['Content-Type' => 'text/plain; charset=utf-8', 'Cache-Control' => 'private, no-store']); } - public function attach(Request $request, string $id, VirtualDesk $desk) + public function attach(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse { $data = $request->validate(['version' => 'required|integer', 'file' => 'nullable|required_without:document_id|file|mimetypes:application/pdf,image/png,image/jpeg,text/plain|max:10240', 'document_id' => 'nullable|required_without:file|uuid', 'category' => 'required|string|max:80', 'stage' => 'nullable|string|max:3', 'request_id' => 'nullable|uuid', 'valid_until' => 'nullable|date_format:Y-m-d', 'source_reference' => 'required|string|max:3000', 'consent' => 'accepted']); - $key = null; $newId = null; + $key = null; + $newId = null; try { DB::transaction(function () use ($request, $id, $desk, $data, &$key, &$newId) { - $case = $desk->case($request->user(), $id, true, true); abort_unless((int) $case->version === (int) $data['version'], 409); - if ($data['stage'] ?? null) abort_unless(array_key_exists($data['stage'], config('virtual-desk.steps')), 422); + $case = $desk->case($request->user(), $id, true, true); + abort_if($case->closed_at, 409, 'Dossier encerrado.'); + abort_unless((int) $case->version === (int) $data['version'], 409); + if ($data['stage'] ?? null) { + abort_unless(array_key_exists($data['stage'], config('virtual-desk.steps')), 422); + } if ($data['request_id'] ?? null) { $row = $desk->request($request->user(), $data['request_id'], true, true); abort_unless($row->case_id === $id && in_array($row->status, ['draft', 'returned'], true), 409, 'Anexos de pedidos submetidos são imutáveis; use a devolução para corrigir.'); } abort_if(DB::table('desk_documents')->where('case_id', $id)->count() >= 100, 422, 'Limite de anexos do dossier atingido.'); if ($request->hasFile('file')) { - $file = $request->file('file'); $hash = hash_file('sha256', $file->getRealPath()); + $file = $request->file('file'); + $hash = hash_file('sha256', $file->getRealPath()); $duplicate = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->where('d.sha256', $hash)->first(); - if ($duplicate) return; - $newId = (string) Str::uuid(); $key = $file->storeAs('virtual-desk/'.$id, $newId, 'local'); abort_unless($key, 500); + if ($duplicate) { + return; + } + $newId = (string) Str::uuid(); + $key = $file->storeAs('virtual-desk/'.$id, $newId, 'local'); + abort_unless(is_string($key) && $key !== '', 500); DB::table('documents')->insert(['id' => $newId, 'source_system' => 'virtual-desk', 'file_name' => basename($file->getClientOriginalName()), 'media_type' => $file->getMimeType(), 'storage_key' => $key, 'sha256' => $hash, 'size_bytes' => $file->getSize(), 'uploaded_by' => $request->user()->id, 'status' => 'quarantined', 'data_classification' => 'confidential']); $documentId = $newId; } else { $documentId = $data['document_id']; - $document = DB::table('documents')->where('id', $documentId)->whereNull('deleted_at')->first(); abort_unless($document, 404); + $document = DB::table('documents')->where('id', $documentId)->whereNull('deleted_at')->first(); + abort_unless($document !== null, 404); $owned = (int) $document->uploaded_by === $request->user()->id || DB::table('desk_documents as l')->join('desk_cases as c', 'c.id', '=', 'l.case_id')->where('l.document_id', $documentId)->where('c.user_id', $request->user()->id)->whereNull('c.organization_id')->exists(); abort_unless($owned, 403, 'Não há autorização para reutilizar este documento.'); - if (DB::table('desk_documents')->where('case_id', $id)->where('document_id', $documentId)->exists()) return; + if (DB::table('desk_documents')->where('case_id', $id)->where('document_id', $documentId)->exists()) { + return; + } } DB::table('desk_documents')->insert(['id' => (string) Str::uuid(), 'case_id' => $id, 'document_id' => $documentId, 'request_id' => $data['request_id'] ?? null, 'category' => $data['category'], 'stage' => $data['stage'] ?? null, 'valid_until' => $data['valid_until'] ?? null, 'source_reference' => $data['source_reference'], 'linked_by' => $request->user()->id]); DB::table('desk_cases')->where('id', $id)->update(['version' => $case->version + 1, 'updated_at' => now()]); $desk->event(DB::table('desk_cases')->where('id', $id)->first(), $request->user(), 'document_linked', ['document_id' => $documentId, 'source' => $data['source_reference'], 'consent' => true]); }); - } catch (\Throwable $e) { if ($key) Storage::disk('local')->delete($key); throw $e; } - if ($newId) ScanDocumentForMalware::dispatch($newId)->afterCommit(); + } catch (\Throwable $e) { + if ($key) { + Storage::disk('local')->delete($key); + } throw $e; + } + if ($newId) { + ScanDocumentForMalware::dispatch($newId)->afterCommit(); + } + return $request->expectsJson() ? response()->json(['ok' => true]) : back()->with('success', 'Documento privado associado; consulte o estado da análise.'); } - public function download(Request $request, string $id, string $document, VirtualDesk $desk) + public function download(Request $request, string $id, string $document, VirtualDesk $desk): BinaryFileResponse { $desk->case($request->user(), $id); - $doc = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->where('l.id', $document)->whereNull('d.deleted_at')->first(['d.*']); + $doc = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->where('l.id', $document)->whereNull('d.deleted_at')->first(['d.*', 'l.request_id']); + if ($doc && $doc->request_id) { + $desk->request($request->user(), $doc->request_id); + } abort_unless($doc && $doc->status === 'active' && $doc->malware_scan_status === 'clean' && trim($doc->sha256) === $doc->malware_scanned_sha256, 404); - $path = realpath(Storage::disk('local')->path($doc->storage_key)); $root = realpath(Storage::disk('local')->path('')); - abort_unless($path && $root && str_starts_with($path, $root.DIRECTORY_SEPARATOR) && is_file($path) && hash_equals(trim($doc->sha256), hash_file('sha256', $path)), 404); + $path = realpath(Storage::disk('local')->path($doc->storage_key)); + $root = realpath(Storage::disk('local')->path('')); + abort_unless($path && $root && str_starts_with($path, $root.DIRECTORY_SEPARATOR) && is_file($path) && is_string($actualHash = hash_file('sha256', $path)) && hash_equals(trim($doc->sha256), $actualHash), 404); $this->auditRead($request, 'documents', $doc->id, 'document_downloaded'); + return response()->download($path, $doc->file_name, ['Cache-Control' => 'private, no-store']); } - public function notification(Request $request, int $id) + public function notification(Request $request, int $id): RedirectResponse { DB::table('desk_notifications')->where('id', $id)->where('user_id', $request->user()->id)->update(['read_at' => now()]); + return back(); } - public function redirectLink(Request $request, string $id, VirtualDesk $desk) + public function redirectLink(Request $request, string $id, VirtualDesk $desk): RedirectResponse { $case = $desk->case($request->user(), $request->validate(['case_id' => 'required|uuid'])['case_id']); - $link = DB::table('desk_institutional_links')->where('id', $id)->where('status', 'active')->whereNotNull('verified_at')->first(); abort_unless($link, 404); + $link = DB::table('desk_institutional_links')->where('id', $id)->first(); + if (! $link || ! app(InstitutionalLinkMonitor::class)->status($link)['available'] || $request->boolean('unavailable')) { + $desk->event($case, $request->user(), 'institutional_link_unavailable', ['link_id' => $id, 'contact' => $link?->contact, 'scope' => 'internal_services_remain_available']); + + return redirect('/balcao-virtual/dossiers/'.$case->id)->with('warning', 'Ligação indisponível ou por verificar. Contacto alternativo: '.($link?->contact ?: (config('portal.support.email') ?: 'utilize o pedido de apoio neste dossier')).'. Pode continuar a preparação e os serviços internos.'); + } abort_unless(filter_var($link->url, FILTER_VALIDATE_URL) && parse_url($link->url, PHP_URL_SCHEME) === 'https', 422); $desk->event($case, $request->user(), 'institutional_referral', ['link_id' => $id, 'version' => $link->version, 'purpose' => $link->purpose]); + // No personal data, case ID or protocol appended to the external URL. return redirect()->away($link->url)->withHeaders(['Referrer-Policy' => 'no-referrer']); } diff --git a/app/Http/Controllers/VirtualDeskInstallationController.php b/app/Http/Controllers/VirtualDeskInstallationController.php new file mode 100644 index 00000000..e1ca7058 --- /dev/null +++ b/app/Http/Controllers/VirtualDeskInstallationController.php @@ -0,0 +1,97 @@ +validate(['version' => 'required|integer|min:1', 'available' => 'required|boolean', + 'evidence' => 'required|string|min:10|max:3000', 'contact' => 'nullable|string|max:255']); + DB::transaction(function () use ($request, $id, $data) { + $link = DB::table('desk_institutional_links')->where('id', $id)->lockForUpdate()->first(); + abort_unless($link !== null, 404); + abort_unless((int) $link->version === (int) $data['version'], 409); + abort_unless(filter_var($link->url, FILTER_VALIDATE_URL) && parse_url($link->url, PHP_URL_SCHEME) === 'https', 422); + DB::table('desk_institutional_links')->where('id', $id)->update(['status' => $data['available'] ? 'active' : 'unavailable', + 'contact' => $data['contact'] ?? $link->contact, 'verified_at' => now(), 'verified_by' => $request->user()->id, + 'version' => $link->version + 1, 'updated_at' => now()]); + DB::table('audit_events')->insert(['source_system' => 'virtual-desk', 'entity_type' => 'desk_institutional_links', 'entity_id' => $id, + 'actor_user_id' => $request->user()->id, 'action' => 'institutional_link_checked', + 'after_data' => json_encode(['url' => $link->url, 'prior_version' => $link->version, ...$data, 'scope' => 'availability_only_not_legal_approval'])]); + }); + + return $request->expectsJson() ? response()->json(['checked' => true]) : back()->with('success', 'Conferência de disponibilidade registada por sete dias.'); + } + + public function sources(Request $request): JsonResponse|Response + { + $read = function (string $name): array { + $file = storage_path('app/private/virtual-desk/'.$name.'.json'); + if (! is_file($file)) { + return []; + } + $bytes = file_get_contents($file); + abort_unless(is_string($bytes), 503); + + return json_decode($bytes, true, 512, JSON_THROW_ON_ERROR); + }; + $inventory = $read('source-inventory'); + $forms = $read('form-register'); + $data = ['total' => count($inventory), 'attachmentCount' => array_sum(array_map(fn ($item) => count($item['attachments'] ?? []), $inventory)), + 'forms' => array_map(fn ($item) => ['identifier' => $item['identifier'], 'sha256' => $item['sha256'], 'size' => $item['size'], 'edition' => $item['edition'], + 'provenance' => str_contains($item['path'], DIRECTORY_SEPARATOR.'Scan'.DIRECTORY_SEPARATOR) ? 'Acervo disponibilizado — digitalização' : 'Acervo disponibilizado — fonte de desenho ou template', + 'consultation' => $item['consultation'], 'legal_documentary_status' => $item['legal_documentary_status'], 'rule_use' => $item['rule_use'], 'official_confirmation' => $item['official_confirmation'], 'fields_status' => $item['fields_status']], $forms), + 'source' => config('virtual-desk.source')]; + + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/sources', $data); + } + + public function preview(Request $request, VirtualDeskInstallation $installation): JsonResponse|Response + { + $data = $installation->preview(); + + return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/install', $data); + } + + public function apply(Request $request, VirtualDeskInstallation $installation): JsonResponse|RedirectResponse + { + $data = $request->validate(['hash' => 'required|string|size:64', 'confirm' => 'accepted']); + $installation->apply($data['hash'], $request->user()); + + return $request->expectsJson() ? response()->json(['installed' => true]) : back()->with('success', 'Instalação concluída; serviços ainda exigem publicação explícita.'); + } + + public function publishService(Request $request): JsonResponse|RedirectResponse + { + $data = $request->validate(['code' => ['required', Rule::in(array_keys(config('virtual-desk.services')))], 'confirm' => 'accepted']); + $row = DB::transaction(function () use ($request, $data) { + DB::select('select pg_advisory_xact_lock(261008, 2)'); + $definition = config('virtual-desk.services.'.$data['code']); + $existing = DB::table('desk_service_versions')->where('code', $data['code'])->orderByDesc('version')->first(); + if ($existing && $existing->status === 'published' && json_decode($existing->definition, true) === $definition) { + return $existing; + } + $id = (string) Str::uuid(); + DB::table('desk_service_versions')->insert(['id' => $id, 'code' => $data['code'], 'version' => ($existing->version ?? 0) + 1, 'status' => 'published', + 'definition' => json_encode($definition), 'source' => json_encode(config('virtual-desk.source')), 'author_id' => $request->user()->id, + 'published_by' => $request->user()->id, 'published_at' => now(), 'created_at' => now(), 'updated_at' => now()]); + DB::table('audit_events')->insert(['source_system' => 'virtual-desk', 'entity_type' => 'desk_service_versions', 'entity_id' => $id, 'actor_user_id' => $request->user()->id, + 'action' => 'internal_service_published', 'after_data' => json_encode(['code' => $data['code'], 'nature' => 'digital_preparation_not_official_form'])]); + + return DB::table('desk_service_versions')->where('id', $id)->first(); + }); + + return $request->expectsJson() ? response()->json(['id' => $row->id], 201) : back()->with('success', 'Serviço de preparação interna publicado.'); + } +} diff --git a/app/Http/Controllers/VirtualDeskOperationsController.php b/app/Http/Controllers/VirtualDeskOperationsController.php new file mode 100644 index 00000000..7a8f58cf --- /dev/null +++ b/app/Http/Controllers/VirtualDeskOperationsController.php @@ -0,0 +1,136 @@ +expectsJson() ? response()->json(['ok' => true]) : back()->with('success', 'Registo interno guardado; não constitui confirmação de uma instituição externa.'); + } + + private function reviewer(Request $request, VirtualDesk $desk, \stdClass $case, ?\stdClass $row): void + { + abort_unless(! $desk->owns($request->user(), $case, false) && ($request->user()->can('manage-portal') || ($row && (int) $row->analyst_id === $request->user()->id)), 403); + } + + private function cleanDocument(string $caseId, string $documentId, ?string $requestId): \stdClass + { + $doc = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id') + ->where('l.case_id', $caseId)->where('d.id', $documentId)->whereNull('d.deleted_at') + ->where(fn ($q) => $q->whereNull('l.request_id')->when($requestId, fn ($p) => $p->orWhere('l.request_id', $requestId))) + ->first(['d.*', 'l.id as link_id']); + abort_unless($doc !== null && $doc->status === 'active' && $doc->malware_scan_status === 'clean' && trim($doc->sha256) === $doc->malware_scanned_sha256, 422, 'Documento indisponível para conferência: verifique autorização, integridade e análise antimalware.'); + $path = realpath(Storage::disk('local')->path($doc->storage_key)); + $root = realpath(Storage::disk('local')->path('')); + abort_unless($path && $root && str_starts_with($path, $root.DIRECTORY_SEPARATOR) && is_file($path) && is_string($hash = hash_file('sha256', $path)) && hash_equals(trim($doc->sha256), $hash), 422, 'O ficheiro original ou a sua integridade não pôde ser confirmado.'); + + return $doc; + } + + public function documentReview(Request $request, string $id, string $document, VirtualDesk $desk): JsonResponse|RedirectResponse + { + $data = $request->validate(['version' => 'required|integer|min:1', 'request_id' => 'nullable|uuid', 'outcome' => 'required|in:internal_verified,internal_issue,contradiction_identified', 'what_checked' => 'required|string|min:3|max:3000', 'evidence' => 'required|string|min:3|max:5000', 'supports' => 'nullable|array|max:4', 'supports.*' => ['string', Rule::in(InvestorGuidanceEvidence::FIELDS)]]); + DB::transaction(function () use ($request, $id, $document, $desk, $data) { + $case = $desk->case($request->user(), $id, false, true); + abort_unless(! $case->closed_at && (int) $case->version === (int) $data['version'], 409); + $link = DB::table('desk_documents')->where('case_id', $id)->where('id', $document)->first(); + abort_unless($link !== null, 404); + $requestId = $link->request_id ?: ($data['request_id'] ?? null); + $row = $requestId ? $desk->request($request->user(), $requestId, false, true) : null; + abort_if($row && $row->case_id !== $id, 422); + $this->reviewer($request, $desk, $case, $row); + $doc = $this->cleanDocument($id, $link->document_id, $requestId); + DB::table('desk_cases')->where('id', $id)->update(['version' => $case->version + 1, 'updated_at' => now()]); + $desk->event(DB::table('desk_cases')->where('id', $id)->firstOrFail(), $request->user(), 'document_reviewed', [ + ...$data, 'document_id' => $doc->id, 'link_id' => $link->id, 'sha256' => trim($doc->sha256), 'document_version' => (int) $doc->version_no, 'reviewer_id' => $request->user()->id, + 'scope' => 'internal_documentary_check_not_official_approval_registration_or_license', + ], $row); + }); + + return $this->result($request); + } + + public function payment(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse + { + $data = $request->validate(['reference' => 'required|string|max:255', 'amount' => ['required', 'regex:/^\d{1,12}(\.\d{1,2})?$/', 'numeric', 'gt:0'], 'currency' => 'required|in:MZN,USD,EUR', 'source_reference' => 'required|string|min:3|max:3000', 'document_id' => 'nullable|uuid', 'consent' => 'accepted']); + DB::transaction(function () use ($request, $id, $desk, $data) { + $row = $desk->request($request->user(), $id, true, true); + $case = $desk->case($request->user(), $row->case_id); + abort_if(in_array($row->status, ['closed', 'cancelled'], true), 409); + if ($data['document_id'] ?? null) { + abort_unless(DB::table('desk_documents')->where('case_id', $case->id)->where('document_id', $data['document_id'])->where(fn ($q) => $q->whereNull('request_id')->orWhere('request_id', $id))->exists(), 422); + } + // References are supplied by the investor; no bank reference or fee is generated. + $existing = DB::table('desk_payments')->where('reference', $data['reference'])->first(); + if ($existing) { + $parts = explode('.', (string) $data['amount']); + $amount = (ltrim($parts[0], '0') ?: '0').'.'.str_pad($parts[1] ?? '', 2, '0'); + abort_unless($existing->request_id === $id && (string) $existing->amount === $amount && $existing->currency === $data['currency'] && $existing->source_reference === $data['source_reference'] && $existing->document_id === ($data['document_id'] ?? null), 409); + + return; + } + $payment = (string) Str::uuid(); + DB::table('desk_payments')->insert(['id' => $payment, 'request_id' => $id, 'reference' => $data['reference'], 'amount' => $data['amount'], 'currency' => $data['currency'], 'source_reference' => $data['source_reference'], 'document_id' => $data['document_id'] ?? null, 'status' => 'pending', 'created_at' => now(), 'updated_at' => now()]); + $desk->event($case, $request->user(), 'payment_declared', ['payment_id' => $payment, 'consent' => true, 'scope' => 'manual_declaration_no_bank_confirmation'], $row); + }); + + return $this->result($request); + } + + public function paymentReview(Request $request, string $id, string $payment, VirtualDesk $desk): JsonResponse|RedirectResponse + { + $data = $request->validate(['version' => 'required|integer|min:1', 'status' => 'required|in:internal_verified,internal_issue', 'reason' => 'required|string|min:3|max:3000', 'evidence' => 'required|string|min:3|max:5000']); + DB::transaction(function () use ($request, $id, $payment, $desk, $data) { + $row = $desk->request($request->user(), $id, false, true); + $case = $desk->case($request->user(), $row->case_id); + $this->reviewer($request, $desk, $case, $row); + abort_if(in_array($row->status, ['closed', 'cancelled'], true), 409); + $record = DB::table('desk_payments')->where('request_id', $id)->where('id', $payment)->lockForUpdate()->first(); + abort_unless($record !== null, 404); + abort_unless((int) $record->version === (int) $data['version'], 409); + $doc = null; + if ($data['status'] === 'internal_verified') { + abort_unless($record->document_id, 422, 'A conferência exige um comprovativo autorizado.'); + $doc = $this->cleanDocument($case->id, $record->document_id, $id); + } + DB::table('desk_payments')->where('id', $payment)->update(['status' => $data['status'], 'reason' => $data['reason'], 'verified_by' => $request->user()->id, 'version' => $record->version + 1, 'updated_at' => now()]); + $desk->event($case, $request->user(), 'payment_reviewed', [...$data, 'payment_id' => $payment, 'document_id' => $doc?->id, 'sha256' => $doc ? trim($doc->sha256) : null, 'reviewer_id' => $request->user()->id, 'scope' => 'internal_proof_check_no_bank_confirmation'], $row); + }); + + return $this->result($request); + } + + public function communication(Request $request, string $id, VirtualDesk $desk): JsonResponse|RedirectResponse + { + $data = $request->validate(['channel' => 'required|in:phone,email,sms,whatsapp,in_person', 'direction' => 'required|in:received,sent', 'occurred_at' => 'required|date|before_or_equal:now', 'summary' => 'required|string|min:3|max:5000', 'evidence' => 'required|string|min:3|max:3000', 'client_key' => 'required|uuid']); + DB::transaction(function () use ($request, $id, $desk, $data) { + $row = $desk->request($request->user(), $id, false, true); + $case = $desk->case($request->user(), $row->case_id); + abort_unless($desk->owns($request->user(), $case) || $request->user()->can('manage-portal') || (int) $row->analyst_id === $request->user()->id, 403); + abort_if(in_array($row->status, ['closed', 'cancelled'], true), 409); + foreach (DB::table('desk_events')->where('request_id', $id)->where('actor_id', $request->user()->id)->where('action', 'manual_communication_recorded')->get(['payload']) as $event) { + $saved = $desk->decode($event->payload); + if (($saved['client_key'] ?? null) === $data['client_key']) { + abort_unless(array_intersect_key($saved, $data) === $data, 409); + + return; + } + } + $desk->event($case, $request->user(), 'manual_communication_recorded', [...$data, 'scope' => 'manual_record_not_portal_delivery_confirmation'], $row); + }); + + return $this->result($request); + } +} diff --git a/app/Http/Middleware/EnsureSimulationRecoveryCookie.php b/app/Http/Middleware/EnsureSimulationRecoveryCookie.php index 856d69ba..a5a0faa8 100644 --- a/app/Http/Middleware/EnsureSimulationRecoveryCookie.php +++ b/app/Http/Middleware/EnsureSimulationRecoveryCookie.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; +use Illuminate\Support\Carbon; use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Response; @@ -44,7 +45,7 @@ final class EnsureSimulationRecoveryCookie $expiry = is_array($payload) ? ($payload['expires_at'] ?? null) : null; if (is_string($expiry)) { try { - $secondsLeft = \Illuminate\Support\Carbon::parse($expiry)->getTimestamp() - now()->getTimestamp(); + $secondsLeft = Carbon::parse($expiry)->getTimestamp() - now()->getTimestamp(); $minutes = max(1, (int) ceil($secondsLeft / 60)); } catch (\Throwable) { // Preserve the standard cookie lifetime if the response has no valid deadline. diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 21de8b27..12e5995d 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -38,10 +38,11 @@ class HandleInertiaRequests extends Middleware return [ ...parent::share($request), 'name' => config('app.name'), + 'portalOperator' => config('portal.operator_name'), 'educationEnabled' => (bool) config('education.enabled', false), 'aiEnabled' => (bool) config('investmoz_ai.enabled', false), 'existingBusinessEnabled' => (bool) config('existing-business.enabled', false), - 'flash' => ['success' => fn () => $request->session()->get('success')], + 'flash' => ['success' => fn () => $request->session()->get('success'), 'warning' => fn () => $request->session()->get('warning')], 'auth' => [ 'user' => $request->user(), 'canManagePortal' => $request->user()?->can('manage-portal') ?? false, diff --git a/app/Http/Middleware/VirtualDeskInstalled.php b/app/Http/Middleware/VirtualDeskInstalled.php new file mode 100644 index 00000000..89d6bca6 --- /dev/null +++ b/app/Http/Middleware/VirtualDeskInstalled.php @@ -0,0 +1,19 @@ +installed(), 503, 'Balcão em preparação. A instalação autenticada exige backup/restauro e QA verificados. Os pedidos existentes continuam no painel anterior.'); + + return $next($request); + } +} diff --git a/app/Http/Requests/StoreSimulationFormDraftRequest.php b/app/Http/Requests/StoreSimulationFormDraftRequest.php index 6caee246..5d850830 100644 --- a/app/Http/Requests/StoreSimulationFormDraftRequest.php +++ b/app/Http/Requests/StoreSimulationFormDraftRequest.php @@ -68,16 +68,16 @@ class StoreSimulationFormDraftRequest extends FormRequest 'economic_snapshot.journey_type' => ['nullable', 'in:personal,small_business,company,investor'], 'economic_snapshot.detail_level' => ['nullable', 'in:simple,advanced'], 'economic_snapshot.business_plan_snapshot' => ['nullable', 'string', 'max:60000'], - 'economic_snapshot.financial_tools_snapshot' => ['nullable', 'string', 'max:30000'], - 'economic_snapshot.project_team_snapshot' => ['nullable', 'string', 'max:30000'], - 'economic_snapshot.project_execution_snapshot' => ['nullable', 'string', 'max:30000'], - 'economic_snapshot.project_risk_responses_snapshot' => ['nullable', 'string', 'max:30000'], - 'economic_snapshot.project_questionnaire_version' => ['nullable', 'string', 'max:100'], - 'economic_snapshot.customer_validation_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], - 'economic_snapshot.cost_estimate_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], - 'economic_snapshot.location_assessment_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], - 'economic_snapshot.receivables_timing_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], - 'economic_snapshot.funding_status' => ['nullable', 'in:confirmed,in_process,not_started,unknown,not_applicable'], + 'economic_snapshot.financial_tools_snapshot' => ['nullable', 'string', 'max:30000'], + 'economic_snapshot.project_team_snapshot' => ['nullable', 'string', 'max:30000'], + 'economic_snapshot.project_execution_snapshot' => ['nullable', 'string', 'max:30000'], + 'economic_snapshot.project_risk_responses_snapshot' => ['nullable', 'string', 'max:30000'], + 'economic_snapshot.project_questionnaire_version' => ['nullable', 'string', 'max:100'], + 'economic_snapshot.customer_validation_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], + 'economic_snapshot.cost_estimate_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], + 'economic_snapshot.location_assessment_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], + 'economic_snapshot.receivables_timing_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'], + 'economic_snapshot.funding_status' => ['nullable', 'in:confirmed,in_process,not_started,unknown,not_applicable'], 'economic_snapshot.manifestation_type' => ['nullable', 'in:investment,business'], 'economic_snapshot.preferred_language' => ['nullable', 'in:pt-MZ,en'], 'economic_snapshot.project_type' => ['nullable', 'in:new,expansion,acquisition,partnership,concession,supplier,buyer,other'], diff --git a/app/Jobs/ScanDocumentForMalware.php b/app/Jobs/ScanDocumentForMalware.php index bc6324e1..134661e9 100644 --- a/app/Jobs/ScanDocumentForMalware.php +++ b/app/Jobs/ScanDocumentForMalware.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Throwable; -class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique +class ScanDocumentForMalware implements ShouldBeUnique, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -31,6 +31,7 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique return $this->documentId; } + /** @return list */ public function backoff(): array { return [15, 60]; @@ -38,7 +39,7 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique public function handle(MalwareScanner $scanner): void { - $document = DB::transaction(function (): ?object { + $document = DB::transaction(function (): ?\stdClass { $row = DB::table('documents')->where('id', $this->documentId)->lockForUpdate()->first(); if (! $row || $row->status === 'deleted' || $row->deleted_at !== null) { return null; @@ -66,8 +67,8 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique $disk = Storage::disk('local'); $root = $disk->path(''); $path = $disk->path($document->storage_key); - $beforeHash = is_file($path) ? hash_file('sha256', $path) : false; - if (! is_string($beforeHash) || ! hash_equals(trim($document->sha256), $beforeHash)) { + $beforeHash = $this->fileHash($path); + if ($beforeHash === null || ! hash_equals(trim($document->sha256), $beforeHash)) { $this->record($document, [ 'result' => 'inconclusive', 'reason_code' => 'file_hash_mismatch', 'engine' => 'ClamAV', 'engine_version' => 'unknown', 'signatures_at' => null, 'duration_ms' => 0, 'detail' => null, @@ -78,8 +79,8 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique $result = $scanner->scan($path, (int) ($document->size_bytes ?? filesize($path)), $root); - $afterHash = is_file($path) ? hash_file('sha256', $path) : false; - if (! is_string($afterHash) || ! hash_equals(trim($document->sha256), $afterHash)) { + $afterHash = $this->fileHash($path); + if ($afterHash === null || ! hash_equals(trim($document->sha256), $afterHash)) { $result['result'] = 'inconclusive'; $result['reason_code'] = 'file_changed_during_scan'; $result['detail'] = null; @@ -102,7 +103,17 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique ], now(), 'failed'); } - private function record(object $document, array $result, \DateTimeInterface $startedAt, string $state): void + private function fileHash(string $path): ?string + { + // Re-read the filesystem after the scanner; files may change meanwhile. + clearstatcache(true, $path); + $hash = is_file($path) ? hash_file('sha256', $path) : false; + + return is_string($hash) ? $hash : null; + } + + /** @param array $result */ + private function record(\stdClass $document, array $result, \DateTimeInterface $startedAt, string $state): void { DB::transaction(function () use ($document, $result, $startedAt, $state): void { $locked = DB::table('documents')->where('id', $this->documentId)->lockForUpdate()->first(); diff --git a/app/Models/CrmTask.php b/app/Models/CrmTask.php index e8b16f7e..1be7895b 100644 --- a/app/Models/CrmTask.php +++ b/app/Models/CrmTask.php @@ -2,7 +2,12 @@ namespace App\Models; -/** @property string|null $lead_id */ +use Carbon\CarbonImmutable; + +/** + * @property string|null $lead_id + * @property CarbonImmutable|null $due_at + */ class CrmTask extends CrmRecord { protected $table = 'tasks'; diff --git a/app/Models/InvestmentSubmission.php b/app/Models/InvestmentSubmission.php index 6e6331c0..7d303946 100644 --- a/app/Models/InvestmentSubmission.php +++ b/app/Models/InvestmentSubmission.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; +/** @property array $payload */ class InvestmentSubmission extends Model { use HasUuids; diff --git a/app/Models/InvestmentZone.php b/app/Models/InvestmentZone.php index c2db24b6..5fa3b4ef 100644 --- a/app/Models/InvestmentZone.php +++ b/app/Models/InvestmentZone.php @@ -5,6 +5,21 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +/** + * @property string $id + * @property string $code + * @property string $name + * @property ?string $primary_territory_id + * @property ?string $province + * @property ?string $district + * @property ?string $physical_category + * @property ?string $legal_regime + * @property ?string $development_status + * @property ?string $availability_status + * @property ?string $area_value + * @property ?string $area_unit + * @property array>|null $source_evidence + */ class InvestmentZone extends DomainRecord { protected $casts = ['version_no' => 'integer', 'published_at' => 'datetime', 'aliases' => 'array', 'activities' => 'array', 'resources' => 'array', 'historical_territory' => 'array', 'area_components' => 'array', 'source_evidence' => 'array', 'area_value' => 'decimal:4', 'data_as_of' => 'date', 'sources_consulted_at' => 'date']; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a47882e9..ab0e352c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,10 +5,10 @@ namespace App\Providers; use App\Models\Opportunity; use App\Models\Sector; use App\Models\User; -use App\Services\Mne\BusinessRepository; -use App\Services\Mne\CompatibleBusinessRepository; use App\Services\Malware\ClamDScanner; use App\Services\Malware\MalwareScanner; +use App\Services\Mne\BusinessRepository; +use App\Services\Mne\CompatibleBusinessRepository; use Carbon\CarbonImmutable; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; diff --git a/app/Services/Ai/EvidenceRepository.php b/app/Services/Ai/EvidenceRepository.php index 318b62c4..4c2a86ca 100644 --- a/app/Services/Ai/EvidenceRepository.php +++ b/app/Services/Ai/EvidenceRepository.php @@ -34,6 +34,10 @@ final class EvidenceRepository if (! empty($filters['nature'])) { $builder->where('s.nature', $filters['nature']); } + if ($filters['public_only'] ?? false) { + $builder->where('d.data_classification', 'public')->where('d.rights_status', '!=', 'restricted') + ->whereExists(fn ($q) => $q->selectRaw('1')->from('portal_resources as r')->whereColumn('r.document_id', 'd.id')->where('r.published', true)->where('r.rights_status', '!=', 'restricted')); + } return array_values($builder->orderByDesc('rank')->orderBy('c.id')->limit((int) config('investmoz_ai.retrieval_limit', 5)) ->get()->map(fn ($row) => (array) $row)->all()); diff --git a/app/Services/Ai/InvestorGuidance.php b/app/Services/Ai/InvestorGuidance.php new file mode 100644 index 00000000..1347fb02 --- /dev/null +++ b/app/Services/Ai/InvestorGuidance.php @@ -0,0 +1,255 @@ + */ + public function context(User $actor, \stdClass $case): array + { + $base = $this->desk->decode($case->payload); + $facts = Arr::only($base['guidance']['assumptions'] ?? [], InvestorGuidanceRules::FIELDS); + $consulted = []; + $unavailable = []; + $financialDraft = null; + $checks = []; + $revision = ['case' => (int) $case->version]; + $submission = $case->submission_id ? InvestmentSubmission::where('id', $case->submission_id)->where('user_id', $actor->id)->first() : null; + if ($submission) { + $input = $submission->payload; + $mapped = ['project_title' => $input['title'] ?? $case->title, 'project_summary' => $input['objective'] ?? '', + 'activity' => $input['activity_description'] ?? '', 'schedule' => $input['schedule'] ?? '']; + $facts = [...$mapped, ...$facts]; + $consulted[] = ['module' => 'investment_submission', 'id' => $submission->id, 'version' => $submission->version, 'date' => $submission->updated_at?->toIso8601String(), 'status' => 'investor_declaration']; + $revision['submission'] = $submission->version; + } elseif ($case->submission_id) { + $unavailable[] = 'investment_submission_permission_or_availability'; + } + if ($case->simulation_id) { + $draft = SimulationFormDraft::where('id', $case->simulation_id)->where('owner_user_id', $actor->id)->whereNull('deleted_at') + ->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))->first(); + if ($draft) { + $economic = SimulationFormDraft::decryptSnapshot($draft->economic_snapshot); + $mapped = Arr::only($economic, InvestorGuidanceRules::FIELDS); + $mapped['activity'] = $economic['selection_labels']['activity_id'] ?? $economic['products_services'] ?? ''; + // A market assumption is not evidence of demand. + if (! empty($economic['market_source'])) { + $mapped['market_evidence'] = $economic['market_source']; + } + $mapped['location'] = $economic['selection_labels']['province_id'] ?? ''; + $mapped['location_evidence'] = $economic['location_rationale'] ?? ''; + if (! empty($economic['skills_needed'])) { + $mapped['resource_plan'] = $economic['skills_needed']; + } + $checks = $this->catalogueChecks($economic); + $facts = [...$mapped, ...$facts]; + // Only economic fields needed by existing deterministic calculators, never personal_snapshot. + $financialDraft = Arr::only($economic, ['business_plan_snapshot', 'financial_tools_snapshot', 'currency']); + $consulted[] = ['module' => 'simulation_form_draft', 'id' => $draft->id, 'version' => $draft->version_no, 'hash' => $draft->snapshot_sha256, + 'date' => $draft->updated_at?->toIso8601String(), 'status' => 'declared_assumptions_and_versioned_snapshots']; + $revision['simulation'] = [$draft->version_no, $draft->snapshot_sha256]; + } else { + $unavailable[] = 'simulation_draft_permission_or_availability'; + } + } + $facts['project_title'] = $facts['project_title'] ?? $case->title; + $evidence = app(InvestorGuidanceEvidence::class)->forCase($actor, $case, $this->desk); + $fingerprint = hash('sha256', json_encode([InvestorGuidanceRules::VERSION, $revision, Arr::sortRecursive($facts), $checks, $evidence], JSON_THROW_ON_ERROR)); + + return ['facts' => $facts, 'consulted' => $consulted, 'unavailable' => $unavailable, 'financial_draft' => $financialDraft, 'fingerprint' => $fingerprint, 'catalogue_checks' => $checks, 'evidence' => $evidence]; + } + + /** @param array $economic + * @return list> */ + private function catalogueChecks(array $economic): array + { + $checks = []; + if (! empty($economic['activity_id'])) { + $activity = DB::table('economic_activities as a')->join('reference_catalog_versions as v', 'v.id', '=', 'a.catalog_version_id') + ->where('a.id', $economic['activity_id'])->where('a.status', 'published')->where('v.status', 'published') + ->whereDate('a.valid_from', '<=', today())->where(fn ($q) => $q->whereNull('a.valid_to')->orWhereDate('a.valid_to', '>', today())) + ->first(['a.id', 'a.sector_id', 'a.catalog_version_id']); + $classification = ! $activity || empty($economic['sector_id']) ? 'insufficient_information' + : ((int) $activity->sector_id === (int) $economic['sector_id'] ? 'catalogue_consistent' : 'documented_incompatibility'); + $checks[] = ['code' => 'activity_sector', 'classification' => $classification, 'restriction' => false, + 'explanation' => match ($classification) { + 'documented_incompatibility' => 'A actividade e o sector seleccionados não correspondem no catálogo publicado consultado.', + 'catalogue_consistent' => 'Actividade e sector correspondem no catálogo consultado; isto não prova mercado ou elegibilidade.', + default => 'Sem referência actual autorizada suficiente para verificar actividade e sector.', + }, 'source' => $activity ? ['module' => 'economic_activities', 'id' => $activity->id, 'version_id' => $activity->catalog_version_id] : null]; + } + $parents = ['district_id' => 'province_id', 'admin_post_id' => 'district_id', 'locality_id' => 'admin_post_id']; + foreach ($parents as $child => $parent) { + if (empty($economic[$child])) { + continue; + } + $location = app(InvestmentGeography::class)->publicReferences()->where('id', $economic[$child])->first(['id', 'parent_id']); + $classification = ! $location || empty($economic[$parent]) ? 'insufficient_information' + : ($location->parent_id === $economic[$parent] ? 'catalogue_consistent' : 'documented_incompatibility'); + $checks[] = ['code' => 'territory_'.$child, 'classification' => $classification, 'restriction' => false, + 'explanation' => match ($classification) { + 'documented_incompatibility' => 'A hierarquia territorial declarada contradiz a referência publicada consultada.', + 'catalogue_consistent' => 'Hierarquia territorial coerente na referência consultada; disponibilidade e infraestrutura continuam por confirmar.', + default => 'Referência territorial insuficiente ou indisponível para esta verificação.', + }, 'source' => $location ? ['module' => 'geo_locations', 'id' => $location->id, 'parent_id' => $location->parent_id] : null]; + } + + return $checks; + } + + /** @return array */ + public function view(User $actor, string $id): array + { + $case = $this->desk->case($actor, $id); + abort_unless($this->installed(), 503, 'A extensão do acompanhamento aguarda instalação autenticada.'); + $context = $this->context($actor, $case); + $choices = []; + foreach (DB::table('desk_events')->where('case_id', $id)->where('action', 'recommendation_reviewed')->orderBy('event_order')->get() as $event) { + $data = $this->desk->decode($event->payload); + $choices[$data['code']] = [...$data, 'actor_id' => $event->actor_id, 'date' => $event->created_at, 'stale' => $data['context_hash'] !== $context['fingerprint']]; + } + $actions = ['opportunities' => '/oportunidades', 'locations' => '/oportunidades?view=map', 'partners' => '/parcerias', + 'documents' => '/documentos', 'plan' => $case->submission_id && (int) $case->user_id === $actor->id ? '/investidor/manifestacoes/'.$case->submission_id : '/balcao-virtual/dossiers/'.$id, + 'simulator' => '/simulador-fiscal', 'support' => '/balcao-virtual/dossiers/'.$id]; + // The simulator recovers its current owner-scoped draft; do not pass private payload in URLs. + if ($case->simulation_id && $context['financial_draft'] !== null) { + $actions['plan'] = '/simulador-fiscal'; + } + $phases = $this->rules->evaluate($context['facts'], $context['evidence']); + foreach ($phases as &$phase) { + foreach ($phase['recommendations'] as &$recommendation) { + $recommendation['review'] = $choices[$recommendation['code']] ?? null; + $recommendation['href'] = $actions[$recommendation['action']]; + } + unset($recommendation); + } + unset($phase); + $tasks = DB::table('tasks as t')->leftJoin('users as u', 'u.id', '=', 't.assignee_user_id')->where('t.desk_case_id', $id)->whereNull('t.deleted_at') + ->when(! $this->desk->owns($actor, $case, false) && ! $actor->can('manage-portal'), fn ($q) => $q->where('t.assignee_user_id', $actor->id)) + ->get(['t.id', 't.title', 't.status', 't.due_at', 't.version_no', 't.desk_request_id', 't.accepted_by', 't.accepted_at', 't.assignee_user_id', 'u.name as assignee_name', 't.external_sync_status']); + + return ['version' => $case->version, 'rules_version' => InvestorGuidanceRules::VERSION, 'computed_at' => now()->toIso8601String(), + ...$context, 'phases' => $phases, 'tasks' => $tasks, 'actions' => $actions, 'can_write' => $this->desk->owns($actor, $case), + 'external_ai' => ['status' => 'not_configured', 'model' => null, 'consulted' => []], + 'calculator_versions' => ['business_financial_plan' => hash_file('sha256', resource_path('js/lib/business-financial-plan.ts')), 'financial_calculators' => hash_file('sha256', resource_path('js/lib/financial-calculators.ts')), 'project_dossier' => hash_file('sha256', resource_path('js/lib/project-dossier.ts'))], + 'crm' => ['internal' => 'available', 'external' => 'out_of_scope', 'reason' => 'O acompanhamento usa o CRM interno existente. A sincronização externa está fora desta fase; nenhum contacto, mensagem ou documento é transmitido.']]; + } + + /** @param array $input */ + public function assumptions(User $actor, string $id, array $input): void + { + $rules = ['version' => 'required|integer|min:1', 'assumptions' => ['required', 'array:'.implode(',', InvestorGuidanceRules::FIELDS)]]; + foreach (InvestorGuidanceRules::FIELDS as $field) { + $rules['assumptions.'.$field] = in_array($field, ['expected_start_date', 'ready_date', 'funding_ready_date', 'completion_date'], true) ? 'nullable|date_format:Y-m-d' : 'nullable|string|max:3000'; + } + $data = Validator::make($input, $rules)->validate(); + DB::transaction(function () use ($actor, $id, $data) { + $case = $this->desk->case($actor, $id, true, true); + abort_if($case->closed_at, 409); + abort_unless((int) $case->version === (int) $data['version'], 409, 'Dossier alterado; recarregue antes de corrigir.'); + $base = $this->desk->decode($case->payload); + $base['guidance']['assumptions'] = $data['assumptions']; + DB::table('desk_cases')->where('id', $id)->update(['payload' => $this->desk->encode($base), 'version' => $case->version + 1, 'updated_at' => now()]); + $this->desk->event(DB::table('desk_cases')->where('id', $id)->first(), $actor, 'assumptions_corrected', ['assumptions' => $data['assumptions'], 'origin' => 'investor_correction_not_official_data']); + }); + } + + /** @param array $input */ + public function feedback(User $actor, string $id, array $input): void + { + $data = Validator::make($input, ['code' => 'required|string|max:100', 'context_hash' => 'required|string|size:64', + 'decision' => ['required', Rule::in(['accepted', 'rejected', 'corrected'])], 'reason' => 'required|string|min:3|max:3000'])->validate(); + DB::transaction(function () use ($actor, $id, $data) { + $case = $this->desk->case($actor, $id, true, true); + abort_if($case->closed_at, 409); + $view = $this->view($actor, $id); + abort_unless(hash_equals($view['fingerprint'], $data['context_hash']), 409, 'Pressupostos alterados; reveja a recomendação actual.'); + $recommendations = array_merge(...array_column($view['phases'], 'recommendations')); + abort_unless(in_array($data['code'], array_column($recommendations, 'code'), true), 422); + $this->desk->event($case, $actor, 'recommendation_reviewed', [...$data, 'rules_version' => InvestorGuidanceRules::VERSION, 'creates_task' => false]); + }); + } + + /** @param array $input */ + public function acceptTask(User $actor, string $id, array $input): CrmTask + { + $data = Validator::make($input, ['code' => 'required|string|max:100', 'context_hash' => 'required|string|size:64', 'consent' => 'accepted', + 'due_at' => 'required|date|after:now', 'request_id' => 'nullable|uuid'])->validate(); + + return DB::transaction(function () use ($actor, $id, $data) { + $case = $this->desk->case($actor, $id, true, true); + abort_if($case->closed_at, 409); + $view = $this->view($actor, $id); + abort_unless(hash_equals($view['fingerprint'], $data['context_hash']), 409); + $recommendations = array_merge(...array_column($view['phases'], 'recommendations')); + $suggestion = collect($recommendations)->firstWhere('code', $data['code']); + abort_unless($suggestion && ($suggestion['review']['decision'] ?? null) === 'accepted' && ! $suggestion['review']['stale'], 422, 'Aceite a recomendação actual antes de assumir a tarefa.'); + if ($data['request_id'] ?? null) { + $request = $this->desk->request($actor, $data['request_id'], true); + abort_unless($request->case_id === $id, 422); + } + // One accepted action per suggestion, even after corrections or retries. + $existing = CrmTask::where('desk_case_id', $id)->where('guidance_key', $data['code'])->first(); + if ($existing) { + abort_unless($existing->getAttribute('desk_request_id') === ($data['request_id'] ?? null) + && (int) $existing->getAttribute('accepted_by') === $actor->id && $existing->due_at !== null && $existing->due_at->equalTo(Carbon::parse($data['due_at'])), 409, 'A tarefa já foi assumida com outros dados; reveja o registo existente.'); + + return $existing; + } + $task = CrmTask::create(['desk_case_id' => $id, 'desk_request_id' => $data['request_id'] ?? null, 'guidance_key' => $data['code'], + 'title' => $suggestion['explanation'], 'status' => 'open', 'due_at' => $data['due_at'], 'assignee_user_id' => $actor->id, + 'accepted_by' => $actor->id, 'accepted_at' => now(), 'external_sync_status' => 'pending_authorization', + 'source_system' => 'investor-guidance', 'data_classification' => 'confidential']); + $this->desk->event($case, $actor, 'guidance_task_accepted', ['task_id' => $task->id, 'code' => $data['code'], 'context_hash' => $data['context_hash'], 'due_at' => $data['due_at'], 'scope' => 'internal_crm']); + + return $task; + }); + } + + /** @param array $input */ + public function updateTask(User $actor, string $id, string $taskId, array $input): void + { + $data = Validator::make($input, ['version' => 'required|integer|min:1', 'status' => ['required', Rule::in(['open', 'in_progress', 'done', 'cancelled'])], + 'reason' => 'required|string|min:3|max:3000', 'assignee_user_id' => 'nullable|integer|exists:users,id', 'due_at' => 'nullable|date|after:now'])->validate(); + DB::transaction(function () use ($actor, $id, $taskId, $data) { + $case = $this->desk->case($actor, $id); + $task = CrmTask::where('desk_case_id', $id)->whereKey($taskId)->lockForUpdate()->firstOrFail(); + abort_unless($actor->can('manage-portal') || (int) $task->getAttribute('assignee_user_id') === $actor->id, 403); + abort_unless((int) $task->getAttribute('version_no') === (int) $data['version'], 409); + $updates = ['status' => $data['status'], 'version_no' => $data['version'] + 1]; + if ($data['assignee_user_id'] ?? null) { + abort_unless($actor->can('manage-portal'), 403); + $assignee = User::where('id', $data['assignee_user_id'])->firstOrFail(); + abort_unless($assignee->hasVerifiedEmail(), 422, 'Responsável sem conta verificada.'); + abort_unless($assignee->can('manage-portal') || DB::table('desk_requests')->where('case_id', $id)->where('analyst_id', $assignee->id)->exists() || $this->desk->owns($assignee, $case), 422, 'Responsável sem acesso ao dossier.'); + $updates['assignee_user_id'] = $assignee->id; + } + if ($data['due_at'] ?? null) { + $updates['due_at'] = $data['due_at']; + } + $task->update($updates); + $this->desk->event($case, $actor, 'guidance_task_updated', ['task_id' => $task->id, ...$data, 'kind' => $actor->can('manage-portal') ? 'responsible_response' : 'investor_action']); + }); + } +} diff --git a/app/Services/Ai/InvestorGuidanceEvidence.php b/app/Services/Ai/InvestorGuidanceEvidence.php new file mode 100644 index 00000000..eac8cb84 --- /dev/null +++ b/app/Services/Ai/InvestorGuidanceEvidence.php @@ -0,0 +1,84 @@ +> */ + public function forCase(User $actor, \stdClass $case, VirtualDesk $desk): array + { + $privateScope = ! $desk->owns($actor, $case, false) && ! $actor->can('manage-portal'); + $documents = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $case->id)->whereNull('d.deleted_at') + ->when($privateScope, fn ($q) => $q->where(fn ($p) => $p->whereNull('l.request_id')->orWhereIn('l.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id')))) + ->get(['l.id as link_id', 'l.document_id', 'l.category', 'l.valid_until', 'd.file_name', 'd.version_no', 'd.sha256', 'd.status', 'd.malware_scan_status', 'd.malware_scanned_sha256', 'd.storage_key']); + $reviews = DB::table('desk_events as e')->join('users as u', 'u.id', '=', 'e.actor_id')->where('e.case_id', $case->id)->where('e.action', 'document_reviewed') + ->when($privateScope, fn ($q) => $q->where(fn ($p) => $p->whereNull('e.request_id')->orWhereIn('e.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id')))) + ->orderByDesc('e.event_order')->get(['e.id', 'e.payload', 'e.created_at', 'e.actor_id', 'u.name']); + $result = []; + foreach (self::FIELDS as $field) { + $records = []; + foreach ($documents as $doc) { + $latest = null; + foreach ($reviews as $review) { + $payload = $desk->decode($review->payload); + if (($payload['link_id'] ?? null) === $doc->link_id) { + $latest = ['id' => $review->id, 'author_id' => $review->actor_id, 'author' => $review->name, 'date' => $review->created_at, 'payload' => $payload]; + break; + } + } + if ($doc->category !== $field && ! ($latest && in_array($field, $latest['payload']['supports'] ?? [], true))) { + continue; + } + // A later review replaces the earlier scope. Merely choosing a + // document category does not verify a preparation assumption. + if ($latest && ! in_array($field, $latest['payload']['supports'] ?? [], true)) { + $latest = null; + } + $state = 'attached_unchecked'; + if ($doc->valid_until && $doc->valid_until < now()->toDateString()) { + $state = 'expired'; + } elseif ($latest && (($latest['payload']['sha256'] ?? null) !== trim($doc->sha256) || ($latest['payload']['document_version'] ?? null) !== (int) $doc->version_no)) { + $state = 'superseded'; + } elseif ($latest) { + $path = realpath(Storage::disk('local')->path($doc->storage_key)); + $root = realpath(Storage::disk('local')->path('')); + if (! $path || ! $root || ! str_starts_with($path, $root.DIRECTORY_SEPARATOR) || ! is_file($path) || ! is_string($hash = hash_file('sha256', $path)) || ! hash_equals(trim($doc->sha256), $hash)) { + $state = 'superseded'; + } elseif ($doc->status !== 'active' || $doc->malware_scan_status !== 'clean' || trim($doc->sha256) !== $doc->malware_scanned_sha256) { + $state = 'technical_unavailable'; + } else { + $state = match ($latest['payload']['outcome'] ?? '') { + 'internal_verified' => 'internally_verified', + 'contradiction_identified' => 'contradiction', + default => 'issue_identified', + }; + } + } + $records[] = ['document_id' => $doc->document_id, 'link_id' => $doc->link_id, 'name' => $doc->file_name, 'state' => $state, 'version' => (int) $doc->version_no, 'hash' => trim($doc->sha256), 'valid_until' => $doc->valid_until, + 'review' => $latest ? ['event_id' => $latest['id'], 'author_id' => $latest['author_id'], 'author' => $latest['author'], 'date' => $latest['date'], 'what_checked' => $latest['payload']['what_checked'], 'evidence' => $latest['payload']['evidence'], 'scope' => $latest['payload']['scope']] : null]; + } + if ($records) { + // A specific current contradiction takes precedence; other valid evidence + // remains visible instead of erasing expired or replaced originals. + $states = array_column($records, 'state'); + $state = 'attached_unchecked'; + foreach (['contradiction', 'internally_verified', 'issue_identified', 'superseded', 'expired', 'technical_unavailable'] as $candidate) { + if (in_array($candidate, $states, true)) { + $state = $candidate; + break; + } + } + $result[$field] = ['state' => $state, 'documents' => $records]; + } + } + + return $result; + } +} diff --git a/app/Services/Ai/InvestorGuidanceRules.php b/app/Services/Ai/InvestorGuidanceRules.php new file mode 100644 index 00000000..8c84b160 --- /dev/null +++ b/app/Services/Ai/InvestorGuidanceRules.php @@ -0,0 +1,121 @@ + $facts + * @param array> $evidence + * @return list> */ + public function evaluate(array $facts, array $evidence = []): array + { + $phases = [ + 'idea' => ['Definir a actividade e o objectivo', ['project_title', 'project_summary', 'activity'], 'Confirme o que pretende produzir ou prestar.', 'plan'], + 'market' => ['Documentar clientes e hipóteses de mercado', ['target_customers', 'market_evidence'], 'Reúna evidência de clientes; uma hipótese não demonstra procura.', 'opportunities'], + 'location' => ['Comparar locais e requisitos da actividade', ['location', 'location_evidence'], 'Confirme terreno, acesso, infraestrutura e licenciamento para cada alternativa.', 'locations'], + 'resources' => ['Preparar recursos e equipa', ['resource_plan', 'resource_evidence'], 'Distinga recursos declarados de disponibilidade comprovada.', 'partners'], + 'finance' => ['Explorar financiamento e cenários', ['funding_evidence'], 'Reveja os pressupostos e os cenários no simulador determinístico.', 'simulator'], + 'execution' => ['Reconciliar calendário e dependências', ['expected_start_date', 'ready_date', 'schedule'], 'Confirme datas, dependências e responsáveis antes de assumir marcos.', 'plan'], + 'support' => ['Preparar o pedido e acompanhar a resposta', [], 'Prepare um rascunho no Balcão; reveja e submeta pessoalmente.', 'support'], + ]; + $result = []; + foreach ($phases as $code => [$objective, $fields, $explanation, $action]) { + $known = []; + $unknown = []; + $phaseEvidence = []; + $risks = []; + foreach ($fields as $field) { + $value = trim((string) ($facts[$field] ?? '')); + $record = $evidence[$field] ?? null; + $state = $record['state'] ?? ($value !== '' ? 'investor_declared_not_verified' : 'absent'); + if (in_array($field, InvestorGuidanceEvidence::FIELDS, true)) { + $phaseEvidence[] = ['field' => $field, 'state' => $state, 'documents' => $record['documents'] ?? []]; + if ($state !== 'internally_verified') { + $message = match ($state) { + 'absent' => 'Ainda não existe informação ou documento para este pressuposto.', + 'investor_declared_not_verified' => 'A declaração do investidor ainda não foi conferida.', + 'attached_unchecked' => 'Existe um documento associado, sem conferência interna deste pressuposto.', + 'expired' => 'A validade documental registada terminou; actualize a evidência.', + 'superseded' => 'O ficheiro ou a versão mudou depois da conferência; é necessária nova análise.', + 'contradiction' => 'Uma conferência interna identificou contradição no âmbito descrito.', + 'issue_identified' => 'Uma conferência interna identificou um problema no âmbito descrito.', + default => 'A evidência não está disponível para utilização; consulte o motivo documental.', + }; + $risks[] = ['code' => $field.'_'.$state, 'classification' => $state === 'contradiction' ? 'documented_contradiction' : 'insufficient_information', 'explanation' => $message, 'field' => $field, 'restriction' => false]; + } + } + if ($value !== '') { + $known[] = ['field' => $field, 'value' => $value, 'status' => 'investor_declared_not_verified']; + } elseif (! $record) { + $unknown[] = $field; + } + } + $result[] = ['id' => $code, 'objective' => $objective, 'known' => $known, 'questions' => $unknown, + 'recommendations' => [[ + 'code' => $code.'_preparation', 'kind' => 'automatic_recommendation', 'classification' => $unknown ? 'insufficient_information' : 'verification_required', + 'explanation' => $explanation, 'action' => $action, 'assumptions' => $known, + 'source' => ['identifier' => self::VERSION, 'reference' => 'app/Services/Ai/InvestorGuidanceRules.php#'.$code, 'date' => '2026-10-08', 'nature' => 'internal_preparation_rule'], + 'limitations' => ['Uma conferência interna só sustenta o âmbito descrito pelo verificador; não aprova procura, infraestrutura, financiamento, registo ou licença institucional.', 'Sem previsão de sucesso, taxa fiscal ou elegibilidade inferida.'], + ]], 'evidence' => $phaseEvidence, 'risks' => $risks]; + } + if (($facts['expected_start_date'] ?? '') && ($facts['ready_date'] ?? '') && $facts['expected_start_date'] < $facts['ready_date']) { + $result[5]['risks'][] = ['code' => 'schedule_contradiction', 'classification' => 'declared_contradiction', + 'explanation' => 'O início indicado antecede a disponibilidade declarada dos recursos.', 'basis' => ['expected_start_date' => $facts['expected_start_date'], 'ready_date' => $facts['ready_date']], + 'restriction' => false]; + } + $result[4]['risks'][] = ['code' => 'tax_eligibility_not_inferred', 'classification' => 'insufficient_information', 'explanation' => 'A evidência do financiamento não determina taxas fiscais ou elegibilidade. Consulte os pressupostos, fontes e versões no simulador.', 'restriction' => false]; + foreach ([ + [1, 'demand_quantity', 'capacity_quantity', ['quantity_unit', 'quantity_period'], 'A procura declarada excede a capacidade declarada para a mesma unidade e período.'], + [3, 'resources_required', 'resources_available', ['resource_unit'], 'Os recursos disponíveis declarados são inferiores aos necessários declarados.'], + [4, 'funding_required', 'funding_committed', ['funding_currency'], 'O financiamento comprometido declarado é inferior ao necessário declarado na mesma moeda.'], + ] as [$phase, $required, $available, $scope, $explanation]) { + $values = array_intersect_key($facts, array_flip([$required, $available, ...$scope])); + if (! array_filter($values, fn ($value) => $value !== null && $value !== '')) { + continue; + } + $complete = true; + foreach ([$required, $available, ...$scope] as $field) { + if (! isset($facts[$field]) || trim((string) $facts[$field]) === '') { + $complete = false; + } + } + // Compare declared magnitudes only: this is no forecast, valuation or financial calculation. + $left = $facts[$required] ?? null; + $right = $facts[$available] ?? null; + $valid = is_scalar($left) && is_scalar($right) + && preg_match('/^\d{1,12}(\.\d{1,2})?$/', (string) $left) + && preg_match('/^\d{1,12}(\.\d{1,2})?$/', (string) $right); + if (! $complete || ! $valid) { + $result[$phase]['risks'][] = ['code' => $required.'_comparison_incomplete', 'classification' => 'insufficient_information', + 'explanation' => 'Faltam valores comparáveis, unidade, período ou moeda para esta comparação.', 'basis' => $values, 'restriction' => false]; + } elseif ($this->declaredMagnitude((string) $left) > $this->declaredMagnitude((string) $right)) { + $result[$phase]['risks'][] = ['code' => $required.'_declared_gap', 'classification' => 'declared_contradiction', + 'explanation' => $explanation.' Estes valores são declarações, não disponibilidade comprovada.', 'basis' => $values, 'restriction' => false]; + } + } + foreach ([['completion_date', 'expected_start_date', 'A conclusão declarada antecede o início.'], + ['expected_start_date', 'funding_ready_date', 'O início declarado antecede a disponibilidade declarada do financiamento.']] as [$first, $second, $explanation]) { + if (! empty($facts[$first]) && ! empty($facts[$second]) && $facts[$first] < $facts[$second]) { + $result[5]['risks'][] = ['code' => $first.'_'.$second.'_contradiction', 'classification' => 'declared_contradiction', + 'explanation' => $explanation, 'basis' => [$first => $facts[$first], $second => $facts[$second]], 'restriction' => false]; + } + } + + return $result; + } + + private function declaredMagnitude(string $value): int + { + $parts = explode('.', $value); + + return (int) $parts[0] * 100 + (int) str_pad($parts[1] ?? '', 2, '0'); + } +} diff --git a/app/Services/InstitutionalLinkMonitor.php b/app/Services/InstitutionalLinkMonitor.php new file mode 100644 index 00000000..29cd68a2 --- /dev/null +++ b/app/Services/InstitutionalLinkMonitor.php @@ -0,0 +1,20 @@ +url, FILTER_VALIDATE_URL) && parse_url($link->url, PHP_URL_SCHEME) === 'https'; + $fresh = $link->verified_at && Carbon::parse($link->verified_at)->between(now()->subDays(7), now()); + + return ['available' => (bool) ($valid && $fresh && $link->status === 'active'), + 'reason' => ! $valid ? 'invalid_url' : (! $fresh ? 'verification_missing_or_expired' : ($link->status === 'active' ? 'recent_internal_check' : 'reported_unavailable')), + 'contact' => $link->contact ?: (config('portal.support.email') ?: 'Utilize o pedido de apoio no mesmo dossier.')]; + } +} diff --git a/app/Services/InvestmentZones.php b/app/Services/InvestmentZones.php index 00c32eff..51a18c81 100644 --- a/app/Services/InvestmentZones.php +++ b/app/Services/InvestmentZones.php @@ -52,7 +52,8 @@ class InvestmentZones }); } - /** @param array $filters */ + /** @param array $filters + * @return Builder */ public function search(array $filters, bool $management = false): Builder { $query = $management ? InvestmentZone::query() : $this->visible(); @@ -118,7 +119,7 @@ class InvestmentZones $zone = InvestmentZone::whereKey($id)->lockForUpdate()->firstOrFail(); abort_if((int) $zone->version_no !== $version, 409, 'Versão alterada. Recarregue antes de aprovar.'); $source = DB::table('investment_legal_sources')->where('id', $zone->legal_source_id)->lockForUpdate()->first(); - $evidence = is_array($zone->source_evidence) ? $zone->source_evidence : []; + $evidence = $zone->source_evidence ?? []; $officialEvidence = collect($evidence)->contains(fn ($item) => in_array($item['source_type'] ?? null, ['government', 'institutional'], true) && str_starts_with((string) ($item['url'] ?? ''), 'https://') && trim((string) ($item['supports'] ?? '')) !== ''); $promoterEvidence = $zone->category === 'industrial_park' && collect($evidence)->contains(fn ($item) => ($item['source_type'] ?? null) === 'promoter' @@ -166,7 +167,7 @@ class InvestmentZones { return DB::transaction(function () use ($id, $version, $actor) { $location = DB::table('investment_zone_locations')->where('id', $id)->lockForUpdate()->first(); - abort_unless($location, 404); + abort_unless($location !== null, 404); abort_if((int) $location->version_no !== $version, 409, 'Version changed.'); abort_if((int) $location->editor_id === $actor, 403, 'Independent review required.'); abort_unless($location->validation_status === 'pending', 422, 'Territorial association already reviewed.'); diff --git a/app/Services/Malware/ArchiveLimitInspector.php b/app/Services/Malware/ArchiveLimitInspector.php index 65b57839..02d29f97 100644 --- a/app/Services/Malware/ArchiveLimitInspector.php +++ b/app/Services/Malware/ArchiveLimitInspector.php @@ -36,7 +36,7 @@ final class ArchiveLimitInspector $expandedBytes = 0; for ($index = 0; $index < $archive->numFiles; $index++) { $entry = $archive->statIndex($index); - if (! is_array($entry) || ! isset($entry['size']) || ! is_int($entry['size']) || $entry['size'] < 0) { + if (! is_array($entry) || $entry['size'] < 0) { return 'archive_inspection_failed'; } diff --git a/app/Services/Malware/ClamDScanner.php b/app/Services/Malware/ClamDScanner.php index bffaa1f9..c20d66c7 100644 --- a/app/Services/Malware/ClamDScanner.php +++ b/app/Services/Malware/ClamDScanner.php @@ -197,7 +197,7 @@ final class ClamDScanner implements MalwareScanner } $offset += $written; $meta = stream_get_meta_data($socket); - if ($meta['timed_out'] ?? false) { + if ($meta['timed_out']) { throw new RuntimeException('ClamD scan timed out while sending data.'); } } @@ -208,19 +208,21 @@ final class ClamDScanner implements MalwareScanner { $reply = stream_get_line($socket, 65536, "\0"); $meta = stream_get_meta_data($socket); - if ($reply === false || ($meta['timed_out'] ?? false)) { - throw new RuntimeException(($meta['timed_out'] ?? false) ? 'ClamD scan timed out.' : 'ClamD returned no scan result.'); + if ($reply === false || $meta['timed_out']) { + throw new RuntimeException($meta['timed_out'] ? 'ClamD scan timed out.' : 'ClamD returned no scan result.'); } return trim($reply); } + /** @return array{available:bool,engine:string,engine_version:?string,signatures_at:?DateTimeImmutable,signature_age_hours:?float,reason_code:?string} */ private function unavailable(string $reason): array { return ['available' => false, 'engine' => 'ClamAV', 'engine_version' => null, 'signatures_at' => null, 'signature_age_hours' => null, 'reason_code' => $reason]; } + /** @return array{result:string,reason_code:?string,engine:string,engine_version:string,signatures_at:?DateTimeImmutable,duration_ms:int,detail:?string} */ private function result(string $result, ?string $reason, int $startedAt, ?string $detail): array { return [ diff --git a/app/Services/Malware/MalwareScanner.php b/app/Services/Malware/MalwareScanner.php index a22532c6..2f5cda0a 100644 --- a/app/Services/Malware/MalwareScanner.php +++ b/app/Services/Malware/MalwareScanner.php @@ -4,7 +4,7 @@ namespace App\Services\Malware; interface MalwareScanner { - /** @return array{result:string,reason_code:?string,engine:string,engine_version:string,signatures_at:\DateTimeImmutable,duration_ms:int,detail:?string} */ + /** @return array{result:string,reason_code:?string,engine:string,engine_version:string,signatures_at:?\DateTimeImmutable,duration_ms:int,detail:?string} */ public function scan(string $path, int $fileSizeBytes, string $storageRoot): array; /** @return array{available:bool,engine:string,engine_version:?string,signatures_at:?\DateTimeImmutable,signature_age_hours:?float,reason_code:?string} */ diff --git a/app/Services/SimulationFormDrafts.php b/app/Services/SimulationFormDrafts.php index 1c0b9787..f1c66cab 100644 --- a/app/Services/SimulationFormDrafts.php +++ b/app/Services/SimulationFormDrafts.php @@ -2,8 +2,8 @@ namespace App\Services; -use App\Models\SimulationFormDraft; use App\Models\ExistingBusiness; +use App\Models\SimulationFormDraft; use App\Models\User; use App\Services\Mne\BusinessRepository; use Illuminate\Database\Eloquent\Builder; @@ -164,6 +164,7 @@ final class SimulationFormDrafts return DB::transaction(function () use ($actorId, $tokenHash): ?SimulationFormDraft { $this->setContext($actorId, $tokenHash); + return $this->ownedQuery($actorId, $tokenHash)->orderByDesc('updated_at')->first(); }); } diff --git a/app/Services/VirtualDesk.php b/app/Services/VirtualDesk.php index 0d635a63..ec32d122 100644 --- a/app/Services/VirtualDesk.php +++ b/app/Services/VirtualDesk.php @@ -2,26 +2,33 @@ namespace App\Services; +use App\Models\InvestmentSubmission; +use App\Models\Investor; +use App\Models\InvestorJourney; use App\Models\User; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Str; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; +use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; class VirtualDesk { public function installed(): bool { - return \Illuminate\Support\Facades\Schema::hasTable('desk_cases'); + return Schema::hasTable('desk_cases') && Schema::hasColumn('desk_events', 'event_order') && Schema::hasColumn('tasks', 'desk_case_id'); } + /** @param array $data */ public function encode(array $data): string { return Crypt::encryptString(json_encode($data, JSON_THROW_ON_ERROR)); } + /** @return array */ public function decode(?string $data): array { return $data ? json_decode(Crypt::decryptString($data), true, 512, JSON_THROW_ON_ERROR) : []; @@ -33,32 +40,36 @@ class VirtualDesk ->when($write, fn ($q) => $q->whereIn('role', ['admin', 'manager', 'officer']))->exists(); } - public function owns(User $actor, object $case, bool $write = true): bool + public function owns(User $actor, \stdClass $case, bool $write = true): bool { return $case->organization_id ? $this->represents($actor, $case->organization_id, $write) : (int) $case->user_id === $actor->id; } - public function case(User $actor, string $id, bool $write = false, bool $lock = false): object + public function case(User $actor, string $id, bool $write = false, bool $lock = false): \stdClass { $case = DB::table('desk_cases')->where('id', $id)->when($lock, fn ($q) => $q->lockForUpdate())->first(); - abort_unless($case, 404); + abort_unless($case !== null, 404); abort_unless($this->owns($actor, $case, $write) || (! $write && ($actor->can('manage-portal') || DB::table('desk_requests')->where('case_id', $id)->where('analyst_id', $actor->id)->exists())), 403); + return $case; } - public function request(User $actor, string $id, bool $ownerOnly = false, bool $lock = false): object + public function request(User $actor, string $id, bool $ownerOnly = false, bool $lock = false): \stdClass { $request = DB::table('desk_requests')->where('id', $id)->when($lock, fn ($q) => $q->lockForUpdate())->first(); - abort_unless($request, 404); + abort_unless($request !== null, 404); $case = $this->case($actor, $request->case_id, $ownerOnly); abort_unless($this->owns($actor, $case, $ownerOnly) || (! $ownerOnly && ($actor->can('manage-portal') || (int) $request->analyst_id === $actor->id)), 403); + return $request; } - public function createCase(User $actor, array $input): object + /** @param array $input */ + public function createCase(User $actor, array $input): \stdClass { $data = Validator::make($input, ['title' => 'required|string|max:200', 'client_key' => 'required|uuid', 'reuse_consent' => 'accepted', - 'organization_id' => 'nullable|uuid', 'company_id' => 'nullable|uuid', 'submission_id' => 'nullable|uuid', 'simulation_id' => 'nullable|uuid'])->validate(); + 'organization_id' => 'nullable|uuid', 'company_id' => 'nullable|uuid', 'submission_id' => 'nullable|uuid', 'simulation_id' => 'nullable|uuid', 'journey_id' => 'nullable|uuid', 'interest_request_id' => 'nullable|integer|min:1'])->validate(); + return DB::transaction(function () use ($actor, $data) { DB::table('users')->where('id', $actor->id)->lockForUpdate()->first(); $existing = DB::table('desk_cases')->where('user_id', $actor->id)->where('client_key', $data['client_key'])->first(); @@ -66,58 +77,132 @@ class VirtualDesk if ($existing) { abort_unless(($this->decode($existing->payload)['_creation_hash'] ?? '') === $inputHash, 409, 'Chave de criação usada com outro conteúdo.'); abort_unless($this->owns($actor, $existing), 403); + return $existing; } abort_if(($data['organization_id'] ?? null) && ! $this->represents($actor, $data['organization_id']), 403); + $interest = null; + if ($data['interest_request_id'] ?? null) { + $interest = DB::table('interest_requests')->where('id', $data['interest_request_id'])->where('metadata->portal_user_id', $actor->id)->whereNull('deleted_at')->firstOrFail(); + $linkedInterest = DB::table('desk_cases')->where('interest_request_id', $data['interest_request_id'])->where('user_id', $actor->id)->first(); + if ($linkedInterest) { + abort_unless($this->owns($actor, $linkedInterest), 403); + foreach (['organization_id', 'submission_id', 'simulation_id', 'company_id', 'journey_id'] as $anchor) { + abort_if(($data[$anchor] ?? null) && ($linkedInterest->{$anchor} ?? null) !== $data[$anchor], 409, 'Pedido existente já associado a um dossier; continue nesse contexto.'); + } + + return $linkedInterest; + } + } + if (($data['journey_id'] ?? null) && Schema::hasColumn('desk_cases', 'journey_id')) { + InvestorJourney::whereKey($data['journey_id'])->where('user_id', $actor->id)->firstOrFail(); + $linkedJourney = DB::table('desk_cases')->where('journey_id', $data['journey_id'])->where('user_id', $actor->id)->first(); + if ($linkedJourney) { + abort_unless($this->owns($actor, $linkedJourney), 403); + foreach (['organization_id', 'submission_id', 'simulation_id', 'company_id'] as $anchor) { + abort_if(($data[$anchor] ?? null) && $linkedJourney->{$anchor} !== $data[$anchor], 409, 'Este percurso já tem um dossier; associe novos elementos no contexto existente.'); + } + + return $linkedJourney; + } + } $payload = ['profile' => app(InvestmentWorkspace::class)->account($actor->id)['payload'], 'name' => $actor->name, 'email' => $actor->email, '_creation_hash' => $inputHash, 'reuse_consent' => ['actor_id' => $actor->id, 'date' => now()->toIso8601String()]]; if ($data['company_id'] ?? null) { $company = DB::table('investment_company_declarations')->where('id', $data['company_id'])->where('user_id', $actor->id)->first(); - abort_unless($company, 404); - $payload['company'] = $this->decode($company->payload); $payload['company_version'] = $company->version; + abort_unless($company !== null, 404); + $payload['company'] = $this->decode($company->payload); + $payload['company_version'] = $company->version; } if ($data['submission_id'] ?? null) { - $submission = \App\Models\InvestmentSubmission::where('id', $data['submission_id'])->where('user_id', $actor->id)->firstOrFail(); - $payload['investment'] = $submission->payload; $payload['investment_version'] = $submission->version; + $submission = InvestmentSubmission::where('id', $data['submission_id'])->where('user_id', $actor->id)->firstOrFail(); + $payload['investment'] = $submission->payload; + $payload['investment_version'] = $submission->version; + } + if ($data['simulation_id'] ?? null) { + app(PartnershipSources::class)->resolve('simulation', $data['simulation_id'], $actor); } - if ($data['simulation_id'] ?? null) app(PartnershipSources::class)->resolve('simulation', $data['simulation_id'], $actor); // One case per explicitly reused investment; retries never create parallel dossiers. if ($data['submission_id'] ?? null) { $linked = DB::table('desk_cases')->where('submission_id', $data['submission_id'])->where('user_id', $actor->id)->first(); - if ($linked) { abort_unless($this->owns($actor, $linked), 403); return $linked; } + if ($linked) { + abort_unless($this->owns($actor, $linked), 403); + foreach (['organization_id', 'simulation_id', 'company_id', 'journey_id'] as $anchor) { + abort_if(($data[$anchor] ?? null) && ($linked->{$anchor} ?? null) !== $data[$anchor], 409, 'Contexto já associado a outro dossier; não foi criado um dossier paralelo.'); + } + + return $linked; + } + } + if ($data['simulation_id'] ?? null) { + $linked = DB::table('desk_cases')->where('simulation_id', $data['simulation_id'])->where('user_id', $actor->id)->first(); + if ($linked) { + abort_unless($this->owns($actor, $linked), 403); + foreach (['organization_id', 'submission_id', 'company_id', 'journey_id'] as $anchor) { + abort_if(($data[$anchor] ?? null) && ($linked->{$anchor} ?? null) !== $data[$anchor], 409, 'Contexto já associado a outro dossier; não foi criado um dossier paralelo.'); + } + + return $linked; + } + } + $identity = []; + if (Schema::hasColumn('desk_cases', 'investor_id')) { + $journey = ($data['journey_id'] ?? null) ? InvestorJourney::whereKey($data['journey_id'])->where('user_id', $actor->id)->firstOrFail() : null; + $account = DB::table('investor_accounts')->where('user_id', $actor->id)->first(); + if (! $account) { + $investor = ($interest->investor_id ?? null) ? Investor::whereKey($interest->investor_id)->firstOrFail() : Investor::create(['legal_name' => $actor->name, 'investor_type' => 'individual', 'status' => 'prospect', 'source_system' => 'portal-account', 'data_classification' => 'confidential']); + DB::table('investor_accounts')->insert(['user_id' => $actor->id, 'investor_id' => $investor->id, 'created_at' => now(), 'updated_at' => now()]); + $account = DB::table('investor_accounts')->where('user_id', $actor->id)->first(); + } + $dossierId = $journey->dossier_id ?? ($submission->dossier_id ?? null); + $projectId = $dossierId ? DB::table('investment_dossiers')->where('id', $dossierId)->whereNull('deleted_at')->value('project_id') : null; + $identity = ['investor_id' => $account->investor_id, 'journey_id' => $journey?->id, 'project_id' => $projectId, 'interest_request_id' => $data['interest_request_id'] ?? null]; } $id = (string) Str::uuid(); DB::table('desk_cases')->insert(['id' => $id, 'user_id' => $actor->id, 'organization_id' => $data['organization_id'] ?? null, 'company_id' => $data['company_id'] ?? null, 'submission_id' => $data['submission_id'] ?? null, 'simulation_id' => $data['simulation_id'] ?? null, 'title' => $data['title'], 'client_key' => $data['client_key'], - 'payload' => $this->encode($payload), 'workflow' => json_encode(array_map(fn ($s) => [...$s, 'status' => 'not_started', 'evidence' => null], config('virtual-desk.steps'))), 'created_at' => now(), 'updated_at' => now()]); - $case = DB::table('desk_cases')->where('id', $id)->first(); + 'payload' => $this->encode($payload), 'workflow' => json_encode(array_map(fn ($s) => [...$s, 'status' => 'not_started', 'evidence' => null], config('virtual-desk.steps'))), 'created_at' => now(), 'updated_at' => now(), ...$identity]); + $case = DB::table('desk_cases')->where('id', $id)->firstOrFail(); $this->event($case, $actor, 'case_created', ['reuse_consent' => true]); + return $case; }); } - public function createRequest(User $actor, string $caseId, string $serviceId, string $clientKey): object + public function createRequest(User $actor, string $caseId, string $serviceId, string $clientKey): \stdClass { abort_unless(Str::isUuid($serviceId) && Str::isUuid($clientKey), 422); + return DB::transaction(function () use ($actor, $caseId, $serviceId, $clientKey) { $case = $this->case($actor, $caseId, true, true); abort_if($case->closed_at, 409, 'Dossier encerrado.'); $service = DB::table('desk_service_versions')->where('id', $serviceId)->where('status', 'published')->first(); - abort_unless($service, 422, 'Versão do serviço indisponível para novos pedidos.'); + abort_unless($service !== null, 422, 'Versão do serviço indisponível para novos pedidos.'); $existing = DB::table('desk_requests')->where('case_id', $caseId)->where('client_key', $clientKey)->first(); - if ($existing) { abort_unless($existing->service_version_id === $serviceId, 409); return $existing; } - $id = (string) Str::uuid(); $base = $this->decode($case->payload); + if ($existing) { + abort_unless($existing->service_version_id === $serviceId, 409); + + return $existing; + } + $id = (string) Str::uuid(); + $base = $this->decode($case->payload); $payload = ['title' => $case->title, 'description' => $base['investment']['objective'] ?? '', 'regime' => 'unknown']; DB::table('desk_requests')->insert(['id' => $id, 'case_id' => $caseId, 'service_version_id' => $serviceId, 'client_key' => $clientKey, 'payload' => $this->encode($payload), 'created_at' => now(), 'updated_at' => now()]); - $request = DB::table('desk_requests')->where('id', $id)->first(); $this->event($case, $actor, 'draft_created', [], $request); + $request = DB::table('desk_requests')->where('id', $id)->firstOrFail(); + $this->event($case, $actor, 'draft_created', [], $request); + return $request; }); } - public function definition(object $request): array + /** @return array */ + public function definition(\stdClass $request): array { return json_decode(DB::table('desk_service_versions')->where('id', $request->service_version_id)->value('definition'), true, 512, JSON_THROW_ON_ERROR); } + /** @param array $payload + * @param array $definition + * @return array */ public function validateFields(array $payload, array $definition, bool $submitting): array { $rules = ['payload' => ['required', 'array:'.implode(',', array_keys($definition['fields']))]]; @@ -126,8 +211,8 @@ class VirtualDesk $required = $submitting && ($field['required'] ?? false) && $active; $rule = [$required ? 'required' : 'nullable']; $rule = [...$rule, ...match ($field['type']) { - 'number' => ['numeric', 'min:'.($field['min'] ?? 0), 'max:999999999999'], 'date' => ['date_format:Y-m-d'], - 'select' => [\Illuminate\Validation\Rule::in(array_keys($field['options']))], 'territory' => ['uuid'], + 'number' => ['numeric', 'decimal:0,2', 'min:'.($field['min'] ?? 0), 'max:999999999999'], 'date' => ['date_format:Y-m-d'], + 'select' => [Rule::in(array_keys($field['options']))], 'territory' => ['uuid'], default => ['string', 'max:'.($field['type'] === 'textarea' ? 15000 : 1000)], }]; $rules['payload.'.$key] = $rule; @@ -135,83 +220,127 @@ class VirtualDesk $validated = Validator::make(['payload' => $payload], $rules)->validate()['payload']; if ($validated['territory_id'] ?? null) { abort_unless(app(InvestmentGeography::class)->publicReferences()->where('id', $validated['territory_id'])->exists(), 422, 'Localização indisponível.'); - if ($submitting && trim($validated['location_source'] ?? '') === '') throw ValidationException::withMessages(['payload.location_source' => 'Indique a fonte da localização.']); + if ($submitting && trim($validated['location_source'] ?? '') === '') { + throw ValidationException::withMessages(['payload.location_source' => 'Indique a fonte da localização.']); + } } + return $validated; } + /** @param array $payload + * @return list> */ public function findings(array $payload): array { $findings = []; - if (isset($payload['investment_amount'], $payload['own_finance'], $payload['other_finance']) && abs((float) $payload['investment_amount'] - (float) $payload['own_finance'] - (float) $payload['other_finance']) > 0.01) { + $amounts = array_map(function ($key) use ($payload) { + $value = (string) ($payload[$key] ?? ''); + if (! preg_match('/^\d{1,12}(?:\.\d{1,2})?$/', $value)) { + return null; + } + [$whole, $fraction] = array_pad(explode('.', $value), 2, ''); + + return (int) $whole * 100 + (int) str_pad($fraction, 2, '0'); + }, ['investment_amount', 'own_finance', 'other_finance']); + if (! in_array(null, $amounts, true) && $amounts[0] !== $amounts[1] + $amounts[2]) { $findings[] = ['code' => 'finance_difference', 'severity' => 'error', 'field' => 'investment_amount', 'explanation' => 'A soma do financiamento difere do valor do investimento.', 'recommendation' => 'Confirme valores e moeda.', 'method' => 'deterministic', 'confidence' => null]; } - if (! ($payload['territory_id'] ?? null)) $findings[] = ['code' => 'location_unknown', 'severity' => 'warning', 'field' => 'territory_id', 'explanation' => 'Localização ainda não documentada.', 'recommendation' => 'Complete quando houver fonte.', 'method' => 'deterministic', 'confidence' => null]; + if (in_array(null, $amounts, true)) { + $findings[] = ['code' => 'finance_unknown', 'severity' => 'warning', 'field' => 'investment_amount', 'explanation' => 'A coerência do financiamento permanece indeterminada sem os três valores válidos na mesma moeda.', 'recommendation' => 'Complete as fontes de financiamento; valores ausentes não são zero.', 'method' => 'deterministic', 'confidence' => null]; + } + if (! ($payload['territory_id'] ?? null)) { + $findings[] = ['code' => 'location_unknown', 'severity' => 'warning', 'field' => 'territory_id', 'explanation' => 'Localização ainda não documentada.', 'recommendation' => 'Complete quando houver fonte.', 'method' => 'deterministic', 'confidence' => null]; + } $findings[] = ['code' => 'official_form_pending', 'severity' => 'warning', 'field' => null, 'explanation' => 'Adaptação digital do plano; não foi identificada uma versão oficial homologada do formulário APIEX no acervo.', 'recommendation' => 'A preparação e análise interna continuam disponíveis; confirme a versão exigida pela entidade para o ato oficial.', 'method' => 'documentary', 'confidence' => null]; + return $findings; } - public function save(User $actor, string $id, array $input, bool $submit = false): object + /** @param array $input */ + public function save(User $actor, string $id, array $input, bool $submit = false): \stdClass { $data = Validator::make($input, ['version' => 'required|integer|min:1', 'payload' => 'required|array', 'consent' => $submit ? 'accepted' : 'nullable|boolean', 'reason' => 'nullable|string|max:3000'])->validate(); + return DB::transaction(function () use ($actor, $id, $data, $submit) { $request = $this->request($actor, $id, true, true); - if ($submit && $request->status === 'submitted' && $request->version === (int) $data['version'] + 1 && hash_equals(hash('sha256', json_encode(Arr::sortRecursive($this->decode($request->payload)))), hash('sha256', json_encode(Arr::sortRecursive($data['payload']))))) return $request; + if ($submit && $request->status === 'submitted' && $request->version === (int) $data['version'] + 1 && hash_equals(hash('sha256', json_encode(Arr::sortRecursive($this->decode($request->payload)), JSON_THROW_ON_ERROR)), hash('sha256', json_encode(Arr::sortRecursive($this->validateFields($data['payload'], $this->definition($request), false)), JSON_THROW_ON_ERROR)))) { + return $request; + } abort_unless(in_array($request->status, ['draft', 'returned'], true) && $request->version === (int) $data['version'], 409, 'Pedido alterado ou já submetido.'); $payload = $this->validateFields($data['payload'], $this->definition($request), $submit); if ($request->status === 'returned') { - $editable = json_decode($request->editable_fields ?? '[]', true); $previous = $this->decode($request->payload); + $editable = json_decode($request->editable_fields ?? '[]', true); + $previous = $this->decode($request->payload); foreach (array_unique([...array_keys($previous), ...array_keys($payload)]) as $key) { - if (! in_array($key, $editable, true) && ($previous[$key] ?? null) !== ($payload[$key] ?? null)) throw ValidationException::withMessages(['payload.'.$key => 'Campo não autorizado para esta correção.']); + if (! in_array($key, $editable, true) && ($previous[$key] ?? null) !== ($payload[$key] ?? null)) { + throw ValidationException::withMessages(['payload.'.$key => 'Campo não autorizado para esta correção.']); + } } abort_if($submit && trim($data['reason'] ?? '') === '', 422, 'Justifique a correção.'); } $findings = $this->findings($payload); - if ($submit && array_filter($findings, fn ($f) => $f['severity'] === 'error')) throw ValidationException::withMessages(['payload.investment_amount' => $findings[0]['explanation']]); + if ($submit && array_filter($findings, fn ($f) => $f['severity'] === 'error')) { + throw ValidationException::withMessages(['payload.investment_amount' => $findings[0]['explanation']]); + } $case = $this->case($actor, $request->case_id, true); $snapshot = ['payload' => $payload, 'service_version_id' => $request->service_version_id, 'documents' => DB::table('desk_documents as link')->join('documents as d', 'd.id', '=', 'link.document_id')->where('link.case_id', $case->id)->where(fn ($q) => $q->whereNull('link.request_id')->orWhere('link.request_id', $id))->get(['link.document_id', 'd.sha256', 'd.version_no', 'link.source_reference'])->all(), 'findings' => $findings, 'consent' => $submit, 'source' => config('virtual-desk.source')]; DB::table('desk_revisions')->insert(['request_id' => $id, 'version' => $request->version, 'snapshot' => $this->encode(['payload' => $this->decode($request->payload), 'status' => $request->status, 'submitted_snapshot' => $request->submitted_snapshot ? $this->decode($request->submitted_snapshot) : null])]); $updates = ['payload' => $this->encode($payload), 'version' => $request->version + 1, 'updated_at' => now()]; - if ($submit) $updates = [...$updates, 'status' => 'submitted', 'protocol' => $request->protocol ?? 'BV-'.date('Y').'-'.strtoupper(str_replace('-', '', $id)), 'submitted_at' => now(), 'submitted_snapshot' => $this->encode($snapshot)]; - DB::table('desk_requests')->where('id', $id)->update($updates); $request = DB::table('desk_requests')->where('id', $id)->first(); + if ($submit) { + $updates = [...$updates, 'status' => 'submitted', 'protocol' => $request->protocol ?? 'BV-'.date('Y').'-'.strtoupper(str_replace('-', '', $id)), 'submitted_at' => now(), 'submitted_snapshot' => $this->encode($snapshot)]; + } + DB::table('desk_requests')->where('id', $id)->update($updates); + $request = DB::table('desk_requests')->where('id', $id)->firstOrFail(); $this->event($case, $actor, $submit ? 'submitted' : 'draft_saved', ['reason' => $data['reason'] ?? '', 'findings' => $findings], $request); + return $request; }); } - public function transition(User $actor, string $id, array $data): object + /** @param array $data */ + public function transition(User $actor, string $id, array $data): \stdClass { - $data = Validator::make($data, ['version' => 'required|integer|min:1', 'status' => ['required', \Illuminate\Validation\Rule::in(array_keys(config('virtual-desk.request_states')))], 'reason' => 'required|string|min:3|max:5000', 'editable_fields' => 'nullable|array', 'editable_fields.*' => 'string|max:100'])->validate(); + $data = Validator::make($data, ['version' => 'required|integer|min:1', 'status' => ['required', Rule::in(array_keys(config('virtual-desk.request_states')))], 'reason' => 'required|string|min:3|max:5000', 'editable_fields' => 'nullable|array', 'editable_fields.*' => 'string|max:100'])->validate(); + return DB::transaction(function () use ($actor, $id, $data) { - $request = $this->request($actor, $id, false, true); $case = $this->case($actor, $request->case_id); + $request = $this->request($actor, $id, false, true); + $case = $this->case($actor, $request->case_id); abort_unless($request->version === (int) $data['version'], 409); $cancel = $data['status'] === 'cancelled'; - if ($cancel) { abort_unless($this->owns($actor, $case) && in_array($request->status, ['draft', 'returned'], true), 403); } - else { + if ($cancel) { + abort_unless($this->owns($actor, $case) && in_array($request->status, ['draft', 'returned'], true), 403); + } else { abort_if($this->owns($actor, $case, false), 403, 'O proponente não pode analisar ou decidir o seu próprio pedido.'); abort_unless($actor->can('manage-portal') || (int) $request->analyst_id === $actor->id, 403); - if (in_array($data['status'], ['accepted', 'rejected', 'closed'], true)) abort_unless($actor->can('manage-portal'), 403); + if (in_array($data['status'], ['accepted', 'rejected', 'closed'], true)) { + abort_unless($actor->can('manage-portal'), 403); + } abort_unless(in_array($data['status'], config('virtual-desk.transitions.'.$request->status, []), true), 409, 'Transição inválida.'); } $editable = $data['editable_fields'] ?? []; abort_if($data['status'] === 'returned' && ! $editable, 422, 'Indique os campos autorizados para correção.'); - abort_if(array_diff($editable, array_keys($this->definition($request)['fields'])), 422); + abort_if(array_diff($editable, array_keys($this->definition($request)['fields'])) !== [], 422); DB::table('desk_requests')->where('id', $id)->update(['status' => $data['status'], 'version' => $request->version + 1, 'editable_fields' => json_encode($editable), 'closed_at' => in_array($data['status'], ['closed', 'cancelled'], true) ? now() : null, 'updated_at' => now()]); - $request = DB::table('desk_requests')->where('id', $id)->first(); $this->event($case, $actor, 'request_'.$data['status'], ['reason' => $data['reason'], 'editable_fields' => $editable], $request); + $request = DB::table('desk_requests')->where('id', $id)->firstOrFail(); + $this->event($case, $actor, 'request_'.$data['status'], ['reason' => $data['reason'], 'editable_fields' => $editable], $request); + return $request; }); } - public function stage(User $actor, string $id, array $data): object + /** @param array $data */ + public function stage(User $actor, string $id, array $data): \stdClass { - $data = Validator::make($data, ['version' => 'required|integer|min:1', 'stage' => ['required', \Illuminate\Validation\Rule::in(array_keys(config('virtual-desk.steps')))], - 'status' => ['required', \Illuminate\Validation\Rule::in(array_keys(config('virtual-desk.step_states')))], 'reason' => 'required|string|min:3|max:3000', + $data = Validator::make($data, ['version' => 'required|integer|min:1', 'stage' => ['required', Rule::in(array_keys(config('virtual-desk.steps')))], + 'status' => ['required', Rule::in(array_keys(config('virtual-desk.step_states')))], 'reason' => 'required|string|min:3|max:3000', 'evidence' => 'nullable|string|max:5000', 'document_id' => 'nullable|uuid', 'valid_until' => 'nullable|date_format:Y-m-d', 'exception' => 'nullable|boolean'])->validate(); + return DB::transaction(function () use ($actor, $id, $data) { $case = $this->case($actor, $id, false, true); abort_unless($this->owns($actor, $case) || $actor->can('manage-portal'), 403); abort_unless((int) $case->version === (int) $data['version'], 409); - $workflow = json_decode($case->workflow, true, 512, JSON_THROW_ON_ERROR); $current = $workflow[$data['stage']]; + $workflow = json_decode($case->workflow, true, 512, JSON_THROW_ON_ERROR); + $current = $workflow[$data['stage']]; $decision = in_array($data['status'], ['completed', 'not_applicable'], true); if ($decision) { abort_unless($actor->can('manage-portal') && ! $this->owns($actor, $case, false), 403, 'Conclusão documental exige revisão interna por outra pessoa autorizada.'); @@ -219,36 +348,63 @@ class VirtualDesk foreach ($current['dependencies'] as $dependency) { abort_unless(in_array($workflow[$dependency]['status'], ['completed', 'not_applicable'], true) || (($data['exception'] ?? false) && ($data['document_id'] ?? null)), 422, 'Dependência '.$dependency.' pendente; uma exceção exige evidência própria.'); } - if ($data['stage'] !== 'E0') abort_unless($data['document_id'] ?? null, 422, 'Ligue o documento de suporte ou de não aplicabilidade.'); + if ($data['stage'] !== 'E0') { + abort_unless($data['document_id'] ?? null, 422, 'Ligue o documento de suporte ou de não aplicabilidade.'); + } } if ($data['document_id'] ?? null) { $document = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->where('d.id', $data['document_id'])->whereNull('d.deleted_at')->first(['d.status', 'd.malware_scan_status', 'd.sha256', 'd.malware_scanned_sha256', 'l.valid_until']); - abort_unless($document, 422); - if ($decision) abort_unless($document->status === 'active' && $document->malware_scan_status === 'clean' && trim($document->sha256) === $document->malware_scanned_sha256 && (! $document->valid_until || $document->valid_until >= now()->toDateString()), 422, 'Documento ainda não consultável ou expirado.'); + abort_unless($document !== null, 422); + if ($decision) { + abort_unless($document->status === 'active' && $document->malware_scan_status === 'clean' && trim($document->sha256) === $document->malware_scanned_sha256 && (! $document->valid_until || $document->valid_until >= now()->toDateString()), 422, 'Documento ainda não consultável ou expirado.'); + } + } + if (in_array($data['status'], ['referred', 'waiting'], true)) { + abort_unless(trim($data['evidence'] ?? '') !== '', 422, 'Registe referência e canal do encaminhamento.'); } - if (in_array($data['status'], ['referred', 'waiting'], true)) abort_unless(trim($data['evidence'] ?? '') !== '', 422, 'Registe referência e canal do encaminhamento.'); // Reopening an upstream step must never leave dependent conclusions falsely valid. if (! in_array($data['status'], ['completed', 'not_applicable'], true)) { $affected = [$data['stage']]; - do { $changed = false; foreach ($workflow as $code => &$step) { if (! in_array($code, $affected, true) && array_intersect($step['dependencies'], $affected)) { $affected[] = $code; $changed = true; if ($step['status'] === 'completed') $step['status'] = 'pending'; } } unset($step); } while ($changed); + do { + $changed = false; + foreach ($workflow as $code => &$step) { + if (! in_array($code, $affected, true) && array_intersect($step['dependencies'], $affected)) { + $affected[] = $code; + $changed = true; + if (in_array($step['status'], ['completed', 'not_applicable'], true)) { + $step['status'] = 'pending'; + } + } + } unset($step); + } while ($changed); } $workflow[$data['stage']] = [...$current, 'status' => $data['status'], 'evidence' => $data['evidence'] ?? null, 'document_id' => $data['document_id'] ?? null, 'valid_until' => $data['valid_until'] ?? null, 'reason' => $data['reason'], 'reviewed_by' => $actor->id, 'updated_at' => now()->toIso8601String()]; DB::table('desk_cases')->where('id', $id)->update(['workflow' => json_encode($workflow), 'version' => $case->version + 1, 'updated_at' => now()]); - $case = DB::table('desk_cases')->where('id', $id)->first(); $this->event($case, $actor, 'stage_updated', $data); + $case = DB::table('desk_cases')->where('id', $id)->firstOrFail(); + $this->event($case, $actor, 'stage_updated', $data); + return $case; }); } - public function event(object $case, User $actor, string $action, array $payload, ?object $request = null): void + /** @param array $payload */ + public function event(\stdClass $case, User $actor, string $action, array $payload, ?\stdClass $request = null): void { - $id = (string) Str::uuid(); $version = $request->version ?? $case->version; + $id = (string) Str::uuid(); + $version = $request->version ?? $case->version; DB::table('desk_events')->insert(['id' => $id, 'case_id' => $case->id, 'request_id' => $request->id ?? null, 'actor_id' => $actor->id, 'action' => $action, 'version' => $version, 'payload' => $this->encode($payload)]); DB::table('audit_events')->insert(['source_system' => 'virtual-desk', 'entity_type' => $request ? 'desk_requests' : 'desk_cases', 'entity_id' => $request->id ?? $case->id, 'actor_user_id' => $actor->id, 'action' => $action, 'after_data' => json_encode(['event_id' => $id, 'event_version' => 1, 'version' => $version, 'case_id' => $case->id, 'source_hash' => config('virtual-desk.source.sha256')])]); $recipients = [(int) $case->user_id]; - if ($case->organization_id) $recipients = [...$recipients, ...DB::table('memberships')->where('organization_id', $case->organization_id)->whereNull('deleted_at')->pluck('user_id')->all()]; - if ($request?->analyst_id) $recipients[] = (int) $request->analyst_id; - foreach (array_unique($recipients) as $user) DB::table('desk_notifications')->insert(['event_id' => $id, 'user_id' => $user, 'title' => config('virtual-desk.request_states.'.$request?->status, 'Dossier atualizado'), 'href' => $request ? '/balcao-virtual/pedidos/'.$request->id : '/balcao-virtual/dossiers/'.$case->id]); + if ($case->organization_id) { + $recipients = [...DB::table('memberships')->where('organization_id', $case->organization_id)->whereNull('deleted_at')->pluck('user_id')->all()]; + } + if ($request?->analyst_id) { + $recipients[] = (int) $request->analyst_id; + } + foreach (array_unique($recipients) as $user) { + DB::table('desk_notifications')->insert(['event_id' => $id, 'user_id' => $user, 'title' => config('virtual-desk.request_states.'.$request?->status, 'Dossier atualizado'), 'href' => $request ? '/balcao-virtual/pedidos/'.$request->id : '/balcao-virtual/dossiers/'.$case->id]); + } } } diff --git a/app/Services/VirtualDeskInstallation.php b/app/Services/VirtualDeskInstallation.php new file mode 100644 index 00000000..5aeeb08d --- /dev/null +++ b/app/Services/VirtualDeskInstallation.php @@ -0,0 +1,102 @@ + database_path('migrations/'.$name.'.php'), self::MIGRATIONS), + ...(glob(resource_path('js/pages/desk/*.tsx')) ?: []), resource_path('js/components/investor-guidance.tsx'), resource_path('js/components/journey-companion.tsx'), resource_path('js/components/desk-internal-operations.tsx'), resource_path('js/layouts/portal-navigation.tsx'), resource_path('js/layouts/app/app-sidebar-layout.tsx'), resource_path('js/pages/investment/shared.tsx'), resource_path('js/lib/investor-guidance-finance.ts')]; + sort($files); + + return hash('sha256', json_encode(array_map(fn ($file) => hash_file('sha256', $file), $files), JSON_THROW_ON_ERROR)); + } + + /** @return array */ + public function preview(): array + { + $identity = DB::selectOne('select current_database() as database, inet_server_port() as port'); + $directory = config('virtual-desk.proof_directory', storage_path('app/private/virtual-desk')); + $read = function ($file) use ($directory) { + if (! is_file($directory.'/'.$file)) { + return []; + } + $contents = file_get_contents($directory.'/'.$file); + abort_unless(is_string($contents), 503, 'Evidência privada indisponível.'); + + return json_decode($contents, true, 512, JSON_THROW_ON_ERROR); + }; + $backup = $read('backup-proof.json'); + $qa = $read('guidance-qa-evidence.json'); + $compatibility = $read('activation-compatibility.json'); + $code = $this->fingerprint(); + $backupVerified = ($backup['restore_verified'] ?? false) && ($backup['target']['database'] ?? null) === $identity->database + && (int) ($backup['target']['port'] ?? 0) === (int) $identity->port && is_file($backup['dump_path'] ?? '') + && hash_file('sha256', $backup['dump_path']) === ($backup['sha256'] ?? null) + && ($backup['files_verified'] ?? false) && is_file($backup['files_path'] ?? '') + && hash_file('sha256', $backup['files_path']) === ($backup['files_sha256'] ?? null) + && strtotime($backup['verified_at'] ?? '') >= time() - 86400; + $qaVerified = ($qa['passed'] ?? false) && ($qa['code_hash'] ?? null) === $code && str_ends_with($qa['database'] ?? '', '_test') && (int) ($qa['port'] ?? 0) === 55441; + $compatibilityVerified = $backupVerified && ($compatibility['passed'] ?? false) && ($compatibility['code_hash'] ?? null) === $code + && ($compatibility['dump_sha256'] ?? null) === ($backup['sha256'] ?? null) + && ($compatibility['empty_rollback_verified'] ?? false) && (int) ($compatibility['port'] ?? 0) === 55441; + $counts = []; + foreach (['desk_cases', 'desk_requests', 'desk_documents', 'desk_messages', 'tasks', 'investor_accounts', 'investment_submissions'] as $table) { + $counts[$table] = Schema::hasTable($table) ? DB::table($table)->count() : null; + } + $installed = app(VirtualDesk::class)->installed(); + $privileges = DB::selectOne("select has_schema_privilege(current_user, 'public', 'CREATE') as can_create, + pg_has_role(current_user, (select tableowner from pg_tables where schemaname='public' and tablename='tasks'), 'USAGE') as can_alter_tasks"); + $databaseReady = (bool) ($privileges->can_create && $privileges->can_alter_tasks); + + return ['hash' => hash('sha256', json_encode([$identity, $code, $counts, $backupVerified, $qaVerified, $compatibilityVerified, $databaseReady], JSON_THROW_ON_ERROR)), 'code_hash' => $code, + 'installed' => $installed, 'target' => $identity, 'counts' => $counts, 'backupVerified' => $backupVerified, 'qaVerified' => $qaVerified, + 'compatibilityVerified' => $compatibilityVerified, 'backupDate' => $backup['verified_at'] ?? null, + 'databaseReady' => $databaseReady, + 'scanner' => app(MalwareScanner::class)->health(), + 'pendingMigrations' => array_values(array_filter(self::MIGRATIONS, fn ($name) => ! DB::table('migrations')->where('migration', $name)->exists())), + 'rollback' => 'Reversão vazia ensaiada em cópia isolada. Com dados novos, preservar/exportar pedidos e tarefas; o down recusa eliminar dados. Restauro integral só numa janela autorizada, após reconciliar alterações posteriores ao backup.', + 'changes' => ['Completar o Balcão Virtual existente, preservando pedidos anteriores.', 'Acrescentar associações opcionais ao investidor, projecto e percurso existentes.', + 'Reutilizar tasks do CRM para acções expressamente assumidas; sem envio externo.', 'Nenhuma publicação de serviço, documento ou candidatura automática.']]; + } + + public function apply(string $hash, User $actor): void + { + abort_unless($actor->can('manage-portal'), 403); + DB::transaction(function () use ($hash, $actor) { + DB::select('select pg_advisory_xact_lock(261008, 1)'); + $preview = $this->preview(); + abort_unless(hash_equals($preview['hash'], $hash), 409, 'Pré-visualização alterada.'); + if ($preview['installed']) { + return; + } + abort_unless($preview['backupVerified'] && $preview['qaVerified'] && $preview['compatibilityVerified'] && $preview['databaseReady'], 422, 'Backup/restauro atualizado, compatibilidade/reversão, permissões da base e QA desta versão continuam necessários.'); + config(['virtual-desk.installation_authorized' => true]); + try { + foreach (self::MIGRATIONS as $name) { + if (! DB::table('migrations')->where('migration', $name)->exists()) { + (require database_path('migrations/'.$name.'.php'))->up(); + DB::table('migrations')->insert(['migration' => $name, 'batch' => (DB::table('migrations')->max('batch') ?? 0) + 1]); + } + } + } finally { + config(['virtual-desk.installation_authorized' => false]); + } + DB::table('audit_events')->insert(['source_system' => 'virtual-desk', 'entity_type' => 'desk_cases', 'actor_user_id' => $actor->id, + 'action' => 'desk_installed', 'after_data' => json_encode(['preview_hash' => $hash, 'code_hash' => $preview['code_hash'], 'prior_counts' => $preview['counts']])]); + }); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index da87347d..b5f1b433 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,14 +1,13 @@ is('api/v1/simulation-drafts*')) { $reference = (string) ($response->headers->get('X-Correlation-ID') ?? $request->attributes->get('correlation_id') diff --git a/config/portal.php b/config/portal.php index a4877104..128746b2 100644 --- a/config/portal.php +++ b/config/portal.php @@ -1,6 +1,7 @@ env('PORTAL_OPERATOR_NAME'), 'support' => [ 'email' => env('PORTAL_SUPPORT_EMAIL'), 'phone' => env('PORTAL_SUPPORT_PHONE'), diff --git a/config/virtual-desk.php b/config/virtual-desk.php index 98df7cd3..d339d7b5 100644 --- a/config/virtual-desk.php +++ b/config/virtual-desk.php @@ -2,7 +2,8 @@ $source = ['identifier' => 'Plano de Incorporação e Optimização do Módulo Balcão Virtual — Portal do Investidor', 'sha256' => 'f167d9156c2572c0253949a6f0ed88aafc28450cce902e8f445e3d979119aa37', 'edition' => 'Plano fornecido em 7 outubro 2026', - 'legal_status' => 'not_a_legal_instrument', 'form_status' => 'digital_preparation', 'rule_use' => 'internal_workflow_only']; + 'legal_status' => 'not_a_legal_instrument', 'form_status' => 'digital_preparation', 'rule_use' => 'internal_workflow_only', + 'legal_claims_status' => 'source_assertions_not_independently_verified', 'official_rates_deadlines_eligibility' => 'indeterminate']; $steps = [ 'E0' => ['Perfil e preparação', [], 'Portal do Investidor', 'Dados mínimos do promotor e do projeto; reutilização consentida.'], 'E1' => ['Reserva do nome', ['E0'], 'SARN', 'Nome aprovado, identificação do pedido, comprovativo e validade.'], @@ -21,6 +22,7 @@ $field = fn ($label, $type = 'text', $required = false, $extra = []) => ['label' $common = ['title' => $field('Nome do projeto', 'text', true), 'description' => $field('Descrição e objetivo', 'textarea', true), 'territory_id' => $field('Localização documentada', 'territory'), 'location_source' => $field('Fonte da localização'), 'regime' => $field('Regime declarado', 'select', false, ['options' => ['unknown' => 'Por determinar', 'registration' => 'Mero registo declarado', 'authorization' => 'Autorização declarada', 'special' => 'Regime especial declarado']])]; + return [ 'installation_authorized' => false, 'source' => $source, diff --git a/database/data/industrial-park-candidate-drafts.json b/database/data/industrial-park-candidate-drafts.json index c43ecfc3..616e0e1b 100644 --- a/database/data/industrial-park-candidate-drafts.json +++ b/database/data/industrial-park-candidate-drafts.json @@ -1,18 +1,291 @@ { - "source_document_attached": true, - "source_document_reconciled": true, - "source_document_title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_document_note": "Documento do utilizador reconciliado com fontes institucionais e primárias acessíveis. Alegações promocionais não são tratadas como actividade autorizada, operação ou disponibilidade.", - "consulted_at": "2026-10-04", - "records": [ - {"code":"MOZ-TOPUITO-PARK","name":"Parque Industrial de Topuito","category":"industrial_park","physical_category":"industrial_park","legal_regime":"pending","development_status":"in_development","availability_status":"reported_unverified","territory_note":"The source names Topuito in Larde; the master dataset has no district row for Larde. This candidate is linked only at Nampula province until the territorial mapping is reviewed.","territories":[{"name":"NAMPULA","type":"province","scope_type":"administrative"}],"source":{"title":"Construction begins on SME Village at Topuito Industrial Park","url":"https://mozparks.co.mz/construction-begins-on-sme-village-at-topuito-industrial-park/","source_type":"promoter","supports":"Project and development activity, not operational availability."},"areas":[{"type":"total","value":215,"unit":"ha","status":"disputed","source_reference":"MozParks brochure 2023, source scope to review"},{"type":"total","value":200,"unit":"ha","status":"disputed","source_reference":"MozParks brochure 2025, source scope to review"},{"type":"developed","value":30,"unit":"ha","status":"reported","source_reference":"MozParks brochure 2025; not a land availability statement"}]}, - {"code":"MOZ-TOPUITO-ZFI","name":"Zona Franca Industrial de Topuito","category":"zfi","physical_category":"industrial_free_zone","legal_regime":"zfi","development_status":"planned","availability_status":"not_informed","territory_note":"The decree names Topuito in Larde; the master dataset has no district row for Larde. This candidate is linked only at Nampula province until the territorial mapping is reviewed.","territories":[{"name":"NAMPULA","type":"province","scope_type":"administrative"}],"source":{"title":"Decreto n.º 57/2023 — criação da ZFI de Topuito","url":"https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-s%C3%A9rie-p%C3%A1g-2349","source_type":"government","supports":"Legal creation and reported legal area; primary review remains required."},"areas":[{"type":"legal","value":65,"unit":"ha","status":"reported","source_reference":"Decreto n.º 57/2023, artigo 1.º — confirm against retained official text"}]}, - {"code":"MOZ-MUNHAVA-PARK","name":"Parque Industrial e Logístico de Munhava","category":"industrial_park","physical_category":"industrial_logistics_park","legal_regime":"pending","development_status":"in_development","availability_status":"not_informed","territories":[{"name":"CIDADE DA BEIRA","type":"district","scope_type":"administrative"}],"source":{"title":"Parque Industrial e Logístico — SDUBeira","url":"https://sdubeira.co.mz/project/parque-industrial-logistico/","source_type":"promoter","supports":"Project existence, location and reported development status."},"areas":[{"type":"total","value":900,"unit":"ha","status":"disputed","source_reference":"SDUBeira project page, total area scope needs confirmation"},{"type":"total","value":990,"unit":"ha","status":"disputed","source_reference":"SDUBeira project headline, conflicts with page summary"},{"type":"pilot","value":50,"unit":"ha","status":"reported","source_reference":"SDUBeira project page; not available-land confirmation"}]}, - {"code":"MOZ-CDP-MONTEPUEZ","name":"Parque Industrial de Montepuez","category":"industrial_park","physical_category":"industrial_park","legal_regime":"pending","development_status":"planned","availability_status":"not_informed","territories":[{"name":"MONTEPUEZ","type":"district","scope_type":"administrative"}],"source":{"title":"MozParks — Cabo Delgado parks initiative","url":"https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/","source_type":"promoter","supports":"District association within a provincial parks initiative; not an operating facility claim."},"areas":[]}, - {"code":"MOZ-CDP-BALAMA","name":"Parque Industrial de Balama","category":"industrial_park","physical_category":"industrial_park","legal_regime":"pending","development_status":"planned","availability_status":"not_informed","territories":[{"name":"BALAMA","type":"district","scope_type":"administrative"}],"source":{"title":"MozParks — Cabo Delgado parks initiative","url":"https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/","source_type":"promoter","supports":"District association within a provincial parks initiative; not an operating facility claim."},"areas":[]}, - {"code":"MOZ-CDP-ANCUABE","name":"Parque Industrial de Ancuabe","category":"industrial_park","physical_category":"industrial_park","legal_regime":"pending","development_status":"planned","availability_status":"not_informed","territories":[{"name":"ANCUABE","type":"district","scope_type":"administrative"}],"source":{"title":"MozParks — Cabo Delgado parks initiative","url":"https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/","source_type":"promoter","supports":"District association within a provincial parks initiative; not an operating facility claim."},"areas":[]}, - {"code":"MOZ-CDP-PALMA","name":"Parque Industrial de Palma","category":"industrial_park","physical_category":"industrial_park","legal_regime":"pending","development_status":"planned","availability_status":"not_informed","territories":[{"name":"PALMA","type":"district","scope_type":"administrative"}],"source":{"title":"MozParks — Cabo Delgado parks initiative","url":"https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/","source_type":"promoter","supports":"District association within a provincial parks initiative; not an operating facility claim."},"areas":[]}, - {"code":"MOZ-REVUBUE-ZFI","name":"Zona Franca Industrial de Revúbuè","category":"zfi","physical_category":"industrial_free_zone","legal_regime":"zfi","development_status":"planned","availability_status":"not_informed","territories":[{"name":"CHIUTA","type":"district","scope_type":"legal_scope"},{"name":"MOATIZE","type":"district","scope_type":"legal_scope"}],"source":{"title":"Revúbuè Free Industrial Zone — APIEX","url":"https://apiex.gov.mz/revubue-free-industrial-zone/","source_type":"institutional","supports":"APIEX-reported identity, district scope and area; primary legal text pending."},"areas":[{"type":"legal","value":4456,"unit":"ha","status":"reported","source_reference":"APIEX page; confirm perimeter against Decreto 41/2016"}]}, - {"code":"MOZ-MINHEWENE-ZFI","name":"Zona Franca Industrial de Minhewene","category":"zfi","physical_category":"industrial_free_zone","legal_regime":"zfi","development_status":"planned","availability_status":"not_informed","territories":[{"name":"NAMPULA","type":"province","scope_type":"administrative"}],"source":{"title":"Zona Franca Industrial de Minhewene — APIEX","url":"https://apiex.gov.mz/zona-franca-industrial-de-minhewene/","source_type":"institutional","supports":"APIEX-reported identity, province and area; primary legal text and site precision pending."},"areas":[{"type":"legal","value":350,"unit":"ha","status":"reported","source_reference":"APIEX page; confirm perimeter against Decreto 51/2011"}]} - ] + "source_document_attached": true, + "source_document_reconciled": true, + "source_document_title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_document_note": "Documento do utilizador reconciliado com fontes institucionais e primárias acessíveis. Alegações promocionais não são tratadas como actividade autorizada, operação ou disponibilidade.", + "consulted_at": "2026-10-04", + "records": [ + { + "code": "MOZ-TOPUITO-PARK", + "name": "Parque Industrial de Topuito", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "pending", + "development_status": "in_development", + "availability_status": "reported_unverified", + "territory_note": "The source names Topuito in Larde; the master dataset has no district row for Larde. This candidate is linked only at Nampula province until the territorial mapping is reviewed.", + "territories": [ + { + "name": "NAMPULA", + "type": "province", + "scope_type": "administrative" + } + ], + "source": { + "title": "Construction begins on SME Village at Topuito Industrial Park", + "url": "https://mozparks.co.mz/construction-begins-on-sme-village-at-topuito-industrial-park/", + "source_type": "promoter", + "supports": "Project and development activity, not operational availability." + }, + "areas": [ + { + "type": "total", + "value": 215, + "unit": "ha", + "status": "disputed", + "source_reference": "MozParks brochure 2023, source scope to review" + }, + { + "type": "total", + "value": 200, + "unit": "ha", + "status": "disputed", + "source_reference": "MozParks brochure 2025, source scope to review" + }, + { + "type": "developed", + "value": 30, + "unit": "ha", + "status": "reported", + "source_reference": "MozParks brochure 2025; not a land availability statement" + } + ] + }, + { + "code": "MOZ-TOPUITO-ZFI", + "name": "Zona Franca Industrial de Topuito", + "category": "zfi", + "physical_category": "industrial_free_zone", + "legal_regime": "zfi", + "development_status": "planned", + "availability_status": "not_informed", + "territory_note": "The decree names Topuito in Larde; the master dataset has no district row for Larde. This candidate is linked only at Nampula province until the territorial mapping is reviewed.", + "territories": [ + { + "name": "NAMPULA", + "type": "province", + "scope_type": "administrative" + } + ], + "source": { + "title": "Decreto n.º 57/2023 — criação da ZFI de Topuito", + "url": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-s%C3%A9rie-p%C3%A1g-2349", + "source_type": "government", + "supports": "Legal creation and reported legal area; primary review remains required." + }, + "areas": [ + { + "type": "legal", + "value": 65, + "unit": "ha", + "status": "reported", + "source_reference": "Decreto n.º 57/2023, artigo 1.º — confirm against retained official text" + } + ] + }, + { + "code": "MOZ-MUNHAVA-PARK", + "name": "Parque Industrial e Logístico de Munhava", + "category": "industrial_park", + "physical_category": "industrial_logistics_park", + "legal_regime": "pending", + "development_status": "in_development", + "availability_status": "not_informed", + "territories": [ + { + "name": "CIDADE DA BEIRA", + "type": "district", + "scope_type": "administrative" + } + ], + "source": { + "title": "Parque Industrial e Logístico — SDUBeira", + "url": "https://sdubeira.co.mz/project/parque-industrial-logistico/", + "source_type": "promoter", + "supports": "Project existence, location and reported development status." + }, + "areas": [ + { + "type": "total", + "value": 900, + "unit": "ha", + "status": "disputed", + "source_reference": "SDUBeira project page, total area scope needs confirmation" + }, + { + "type": "total", + "value": 990, + "unit": "ha", + "status": "disputed", + "source_reference": "SDUBeira project headline, conflicts with page summary" + }, + { + "type": "pilot", + "value": 50, + "unit": "ha", + "status": "reported", + "source_reference": "SDUBeira project page; not available-land confirmation" + } + ] + }, + { + "code": "MOZ-CDP-MONTEPUEZ", + "name": "Parque Industrial de Montepuez", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "pending", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "MONTEPUEZ", + "type": "district", + "scope_type": "administrative" + } + ], + "source": { + "title": "MozParks — Cabo Delgado parks initiative", + "url": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "source_type": "promoter", + "supports": "District association within a provincial parks initiative; not an operating facility claim." + }, + "areas": [] + }, + { + "code": "MOZ-CDP-BALAMA", + "name": "Parque Industrial de Balama", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "pending", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "BALAMA", + "type": "district", + "scope_type": "administrative" + } + ], + "source": { + "title": "MozParks — Cabo Delgado parks initiative", + "url": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "source_type": "promoter", + "supports": "District association within a provincial parks initiative; not an operating facility claim." + }, + "areas": [] + }, + { + "code": "MOZ-CDP-ANCUABE", + "name": "Parque Industrial de Ancuabe", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "pending", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "ANCUABE", + "type": "district", + "scope_type": "administrative" + } + ], + "source": { + "title": "MozParks — Cabo Delgado parks initiative", + "url": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "source_type": "promoter", + "supports": "District association within a provincial parks initiative; not an operating facility claim." + }, + "areas": [] + }, + { + "code": "MOZ-CDP-PALMA", + "name": "Parque Industrial de Palma", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "pending", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "PALMA", + "type": "district", + "scope_type": "administrative" + } + ], + "source": { + "title": "MozParks — Cabo Delgado parks initiative", + "url": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "source_type": "promoter", + "supports": "District association within a provincial parks initiative; not an operating facility claim." + }, + "areas": [] + }, + { + "code": "MOZ-REVUBUE-ZFI", + "name": "Zona Franca Industrial de Revúbuè", + "category": "zfi", + "physical_category": "industrial_free_zone", + "legal_regime": "zfi", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "CHIUTA", + "type": "district", + "scope_type": "legal_scope" + }, + { + "name": "MOATIZE", + "type": "district", + "scope_type": "legal_scope" + } + ], + "source": { + "title": "Revúbuè Free Industrial Zone — APIEX", + "url": "https://apiex.gov.mz/revubue-free-industrial-zone/", + "source_type": "institutional", + "supports": "APIEX-reported identity, district scope and area; primary legal text pending." + }, + "areas": [ + { + "type": "legal", + "value": 4456, + "unit": "ha", + "status": "reported", + "source_reference": "APIEX page; confirm perimeter against Decreto 41/2016" + } + ] + }, + { + "code": "MOZ-MINHEWENE-ZFI", + "name": "Zona Franca Industrial de Minhewene", + "category": "zfi", + "physical_category": "industrial_free_zone", + "legal_regime": "zfi", + "development_status": "planned", + "availability_status": "not_informed", + "territories": [ + { + "name": "NAMPULA", + "type": "province", + "scope_type": "administrative" + } + ], + "source": { + "title": "Zona Franca Industrial de Minhewene — APIEX", + "url": "https://apiex.gov.mz/zona-franca-industrial-de-minhewene/", + "source_type": "institutional", + "supports": "APIEX-reported identity, province and area; primary legal text and site precision pending." + }, + "areas": [ + { + "type": "legal", + "value": 350, + "unit": "ha", + "status": "reported", + "source_reference": "APIEX page; confirm perimeter against Decreto 51/2011" + } + ] + } + ] } diff --git a/database/data/industrial-parks-reconciliation.json b/database/data/industrial-parks-reconciliation.json index 820b4a7b..a69a79bc 100644 --- a/database/data/industrial-parks-reconciliation.json +++ b/database/data/industrial-parks-reconciliation.json @@ -1,271 +1,248 @@ { - "source_document": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "provided": true, - "received_at": "2026-10-04", - "classification": "documento fornecido pelo utilizador; referncias citadas sem URLs/ficheiros primrios anexos; claims no so por si s prova oficial", - "note": "As afirmaes do documento foram comparadas com pginas institucionais/promotoras e referncias oficiais acessveis. Manter alegaes promocionais separadas de existncia jurdica, autorizao de actividade, operao e disponibilidade." - }, - "consulted_at": "2026-10-04", - "candidates": [ - { - "name": "Beluluane", - "outcome": "reconcile_existing", - "prepared_codes": [ - "MOZ-BELULUANE-PARK", - "MOZ-BELULUANE-ZFI" - ], - "source": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", - "known": "MozParks apresenta 700 ha prontos para arrendamento; APIEX apresenta complexo de cerca de 780 ha: parque 600 ha, Mozal 180 ha e ZFI 55 ha. So mbitos que no podem ser somados nem equiparados a disponibilidade.", - "pending": "Confirmar limites e sobreposio cartogrfica/documental, sobretudo relao entre 700 ha e parque de 600 ha. No publicar actividades como autorizadas nem declarar lotes disponveis.", - "user_document_claims": { - "status": "operacional e em expanso (alegao documental/promotora)", - "area": "700 ha (alegao MozParks no portal actual); actividades/enfoque: manufactura, alumnio, cabos, agro-processamento, tecnologia, logstica e servios", - "assessment": "MozParks apresenta 700 ha prontos para arrendamento; APIEX descreve um complexo de cerca de 780 ha composto por parque de 600 ha e Mozal 180 ha, e uma ZFI de 55 ha. Escopos no reconciliados; nenhum nmero prova disponibilidade actual." - }, - "claim_source": { + "source_document": { "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } + "provided": true, + "received_at": "2026-10-04", + "classification": "documento fornecido pelo utilizador; referncias citadas sem URLs/ficheiros primrios anexos; claims no so por si s prova oficial", + "note": "As afirmaes do documento foram comparadas com pginas institucionais/promotoras e referncias oficiais acessveis. Manter alegaes promocionais separadas de existncia jurdica, autorizao de actividade, operao e disponibilidade." }, - { - "name": "Topuito", - "outcome": "prepare_separate_park_and_zfi", - "prepared_codes": [ - "MOZ-TOPUITO-PARK", - "MOZ-TOPUITO-ZFI" - ], - "source": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-s%C3%A9rie-p%C3%A1g-2349", - "known": "MozParks apresenta Topuito como parque e oferta de 215 ha; notcia de Agosto de 2026 confirma incio da construo do SME Village. Decreto 57/2023 cria separadamente ZFI com rea legal de 65 ha.", - "pending": "O parque, a obra do SME Village e a ZFI so escopos distintos. Verificar limites, operao e disponibilidade actual. Base territorial no contm distrito Larde; manter associao apenas provincial at reviso.", - "user_document_claims": { - "status": "operacional/em expanso (alegao documental/promotora)", - "area": "215 ha (documento e perfil institucional MozParks)", - "assessment": "MozParks identifica Topuito Industrial Park e notcia de Agosto de 2026 informa incio de construo do SME Village. A ZFI legal de 65 ha entidade/permetro distinto. No inferir que toda a rea esteja pronta ou disponvel." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } + "consulted_at": "2026-10-04", + "candidates": [ + { + "name": "Beluluane", + "outcome": "reconcile_existing", + "prepared_codes": ["MOZ-BELULUANE-PARK", "MOZ-BELULUANE-ZFI"], + "source": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", + "known": "MozParks apresenta 700 ha prontos para arrendamento; APIEX apresenta complexo de cerca de 780 ha: parque 600 ha, Mozal 180 ha e ZFI 55 ha. So mbitos que no podem ser somados nem equiparados a disponibilidade.", + "pending": "Confirmar limites e sobreposio cartogrfica/documental, sobretudo relao entre 700 ha e parque de 600 ha. No publicar actividades como autorizadas nem declarar lotes disponveis.", + "user_document_claims": { + "status": "operacional e em expanso (alegao documental/promotora)", + "area": "700 ha (alegao MozParks no portal actual); actividades/enfoque: manufactura, alumnio, cabos, agro-processamento, tecnologia, logstica e servios", + "assessment": "MozParks apresenta 700 ha prontos para arrendamento; APIEX descreve um complexo de cerca de 780 ha composto por parque de 600 ha e Mozal 180 ha, e uma ZFI de 55 ha. Escopos no reconciliados; nenhum nmero prova disponibilidade actual." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Topuito", + "outcome": "prepare_separate_park_and_zfi", + "prepared_codes": ["MOZ-TOPUITO-PARK", "MOZ-TOPUITO-ZFI"], + "source": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-s%C3%A9rie-p%C3%A1g-2349", + "known": "MozParks apresenta Topuito como parque e oferta de 215 ha; notcia de Agosto de 2026 confirma incio da construo do SME Village. Decreto 57/2023 cria separadamente ZFI com rea legal de 65 ha.", + "pending": "O parque, a obra do SME Village e a ZFI so escopos distintos. Verificar limites, operao e disponibilidade actual. Base territorial no contm distrito Larde; manter associao apenas provincial at reviso.", + "user_document_claims": { + "status": "operacional/em expanso (alegao documental/promotora)", + "area": "215 ha (documento e perfil institucional MozParks)", + "assessment": "MozParks identifica Topuito Industrial Park e notcia de Agosto de 2026 informa incio de construo do SME Village. A ZFI legal de 65 ha entidade/permetro distinto. No inferir que toda a rea esteja pronta ou disponvel." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Munhava", + "outcome": "prepare_informational_candidate", + "prepared_codes": ["MOZ-MUNHAVA-PARK"], + "source": "https://sdubeira.co.mz/project/parque-industrial-logistico/", + "known": "SDUBeira descreve o projecto em fases, rea total de 900 ha no resumo e piloto de 50 ha; o destaque de 990 ha na mesma pgina cria divergncia.", + "pending": "Documento de entrada acrescenta faixa aproximada de 44-60 ha para piloto; no corresponde a metragem confirmada. Obter permetro/plano primrio e no declarar terreno disponvel.", + "user_document_claims": { + "status": "desenvolvimento faseado", + "area": "900 ha total e piloto de cerca de 44-60 ha (alegao documental); actividades/enfoque: logstica porturia, armazenagem, manufactura, agro-indstria e pescas", + "assessment": "SDUBeira descreve 900 ha no resumo e 50 ha piloto; a mesma pgina contm destaque de 990 ha. Conservar divergncia editorial e no tratar piloto como terreno livre." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Montepuez", + "outcome": "prepare_district_candidate", + "prepared_codes": ["MOZ-CDP-MONTEPUEZ"], + "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Montepuez. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", + "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", + "user_document_claims": { + "status": "planeado/em desenvolvimento", + "activities": "fornecedores mineiros, transformao, servios e PME (enfoque alegado no documento)", + "assessment": "MozParks confirma quatro parques em desenvolvimento nos distritos; actividades so foco de promoo, no licena ou operao confirmada." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Balama", + "outcome": "prepare_district_candidate", + "prepared_codes": ["MOZ-CDP-BALAMA"], + "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Balama. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", + "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", + "user_document_claims": { + "status": "planeado/em desenvolvimento", + "activities": "minerao, servios industriais, fornecedores e PME (enfoque alegado no documento)", + "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Ancuabe", + "outcome": "prepare_district_candidate", + "prepared_codes": ["MOZ-CDP-ANCUABE"], + "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Ancuabe. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", + "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", + "user_document_claims": { + "status": "planeado/em desenvolvimento", + "activities": "transformao, contedo local, servios industriais e PME (enfoque alegado no documento)", + "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Palma", + "outcome": "prepare_district_candidate", + "prepared_codes": ["MOZ-CDP-PALMA"], + "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Palma. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", + "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", + "user_document_claims": { + "status": "planeado/em desenvolvimento", + "activities": "petrleo e gs, energia, logstica e cadeias locais (enfoque alegado no documento)", + "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Selva/Manica Parks", + "outcome": "keep_unresolved_program_and_site_distinct", + "prepared_codes": [], + "source": "https://mozparks.co.mz/mozparks-launches-manica-parks-a-new-industrial-platform-for-manica-province/", + "known": "MozParks confirma Manica Parks como iniciativa provincial lanada por memorando em Setembro de 2026. O documento fornecido associa a iniciativa a Selva/Vanduzi, mas no confirma instalao individual.", + "pending": "No criar parque individual nem relao territorial de Selva sem fonte especfica. Actividades listadas so enfoque declarado, no autorizao.", + "user_document_claims": { + "status": "Manica Parks lanado por memorando em Setembro de 2026; alegao de localizao em Selva/Vanduzi permanece por confirmar", + "activities": "agro-processamento, metalomecnica, combustveis, hotelaria e restaurao (enfoque alegado no documento)", + "assessment": "MozParks confirma iniciativa provincial e memorando, no uma instalao individual em Selva nem incio de construo/operao." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Mocuba ZFI", + "outcome": "reconcile_existing", + "prepared_codes": ["MOZ-MOCUBA-ZFI", "MOZ-MOCUBA-ZEE"], + "source": "https://apiex.gov.mz/zif/mocuba-free-industrial-zone/", + "known": "APIEX confirma a ZFI de 50,58 ha e fase de mobilizao de recursos e operador. A lista de actividades do documento enfoque declarado, no autorizao.", + "pending": "Confirmar diploma primrio, limites e actividades; manter ZFI e ZEE de Mocuba separadas.", + "user_document_claims": { + "status": "mobilizao de recursos e operador", + "area": "50,58 ha segundo APIEX", + "activities": "agro-florestal, pescas, txteis, calcrio, pedra e minerais (enfoque alegado no documento)", + "assessment": "APIEX confirma 50,58 ha e mobilizao; confirmar actividades com fonte prpria antes de atribuir ao registo. Manter ZFI distinta da ZEE." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + }, + { + "name": "Revúbuè ZFI", + "outcome": "prepare_one_record_two_districts", + "prepared_codes": ["MOZ-REVUBUE-ZFI"], + "source": "https://apiex.gov.mz/revubue-free-industrial-zone/", + "known": "APIEX reporta a ZFI abrangendo Chiuta e Moatize, 4.456 ha, sem operador identificado.", + "pending": "Obter e validar texto primário do Decreto 41/2016 e as relações territoriais. Um único registo, duas localizações.", + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Mocambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + }, + "user_document_claims": { + "status": "sem operador; um projecto aprovado segundo APIEX", + "area": "4 456 ha segundo APIEX", + "activities": "industria ligada a recursos e transformacao (enfoque alegado no documento)", + "assessment": "APIEX informa um projecto aprovado e potenciais sectores; isto nao transforma o projecto em oportunidade publicada nem comprova actividades autorizadas." + } + }, + { + "name": "Minhewene ZFI", + "outcome": "prepare_province_precision_candidate", + "prepared_codes": ["MOZ-MINHEWENE-ZFI"], + "source": "https://apiex.gov.mz/zona-franca-industrial-de-minhewene/", + "known": "Pgina APIEX actualmente acessvel reporta Decreto 51/2011, Nampula/Cidade de Nacala, 350 ha e mobilizao de recursos/operador. O documento fornecido diz que a ficha no estava acessvel; esta afirmao diverge da consulta actual.", + "pending": "Validar Decreto 51/2011 e localizao cadastral exacta; pgina institucional no substitui fonte legal primria nem comprova operacionalizao actual.", + "user_document_claims": { + "status": "mobilizao de recursos e operador segundo pgina APIEX acessvel na verificao actual", + "area": "350 ha segundo APIEX", + "assessment": "A alegao do documento de que no havia ficha acessvel diverge da pgina institucional APIEX actualmente acessvel, que reporta Decreto 51/2011, Nampula/Cidade de Nacala, 350 ha e mobilizao de recursos/operador. Acessibilidade da pgina no substitui validao do diploma primrio ou do estado actual." + }, + "claim_source": { + "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", + "source_type": "user_supplied_secondary_document", + "verified_as_primary": false + } + } + ], + "legislative_review": { + "decree_57_2023": "Texto oficial localizado; criação da ZFI de Topuito e 65 ha confirmados. Não implica que operador, actividade operacional ou terrenos disponíveis estejam confirmados.", + "decree_12_2026": "A página oficial consultada apresenta o sumário de aprovação do regulamento ZEE/ZFI e revogação do Decreto 75/99. O texto integral e regras transitórias/entrada em vigor ainda não foram revistos; não parametrizar simulador fiscal a partir do sumário." }, - { - "name": "Munhava", - "outcome": "prepare_informational_candidate", - "prepared_codes": [ - "MOZ-MUNHAVA-PARK" - ], - "source": "https://sdubeira.co.mz/project/parque-industrial-logistico/", - "known": "SDUBeira descreve o projecto em fases, rea total de 900 ha no resumo e piloto de 50 ha; o destaque de 990 ha na mesma pgina cria divergncia.", - "pending": "Documento de entrada acrescenta faixa aproximada de 44-60 ha para piloto; no corresponde a metragem confirmada. Obter permetro/plano primrio e no declarar terreno disponvel.", - "user_document_claims": { - "status": "desenvolvimento faseado", - "area": "900 ha total e piloto de cerca de 44-60 ha (alegao documental); actividades/enfoque: logstica porturia, armazenagem, manufactura, agro-indstria e pescas", - "assessment": "SDUBeira descreve 900 ha no resumo e 50 ha piloto; a mesma pgina contm destaque de 990 ha. Conservar divergncia editorial e no tratar piloto como terreno livre." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Montepuez", - "outcome": "prepare_district_candidate", - "prepared_codes": [ - "MOZ-CDP-MONTEPUEZ" - ], - "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", - "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Montepuez. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", - "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", - "user_document_claims": { - "status": "planeado/em desenvolvimento", - "activities": "fornecedores mineiros, transformao, servios e PME (enfoque alegado no documento)", - "assessment": "MozParks confirma quatro parques em desenvolvimento nos distritos; actividades so foco de promoo, no licena ou operao confirmada." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Balama", - "outcome": "prepare_district_candidate", - "prepared_codes": [ - "MOZ-CDP-BALAMA" - ], - "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", - "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Balama. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", - "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", - "user_document_claims": { - "status": "planeado/em desenvolvimento", - "activities": "minerao, servios industriais, fornecedores e PME (enfoque alegado no documento)", - "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Ancuabe", - "outcome": "prepare_district_candidate", - "prepared_codes": [ - "MOZ-CDP-ANCUABE" - ], - "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", - "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Ancuabe. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", - "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", - "user_document_claims": { - "status": "planeado/em desenvolvimento", - "activities": "transformao, contedo local, servios industriais e PME (enfoque alegado no documento)", - "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Palma", - "outcome": "prepare_district_candidate", - "prepared_codes": [ - "MOZ-CDP-PALMA" - ], - "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", - "known": "A iniciativa MozParks menciona parque/rede de Cabo Delgado no distrito de Palma. MozParks confirma a rede/parques em desenvolvimento; actividades do documento so apenas enfoque promocional.", - "pending": "Regime jurdico, reas, actividades efectivamente autorizadas/instaladas e disponibilidade permanecem por confirmar.", - "user_document_claims": { - "status": "planeado/em desenvolvimento", - "activities": "petrleo e gs, energia, logstica e cadeias locais (enfoque alegado no documento)", - "assessment": "MozParks confirma a iniciativa/distrito; actividades permanecem promocionais e no so autorizaes." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Selva/Manica Parks", - "outcome": "keep_unresolved_program_and_site_distinct", - "prepared_codes": [], - "source": "https://mozparks.co.mz/mozparks-launches-manica-parks-a-new-industrial-platform-for-manica-province/", - "known": "MozParks confirma Manica Parks como iniciativa provincial lanada por memorando em Setembro de 2026. O documento fornecido associa a iniciativa a Selva/Vanduzi, mas no confirma instalao individual.", - "pending": "No criar parque individual nem relao territorial de Selva sem fonte especfica. Actividades listadas so enfoque declarado, no autorizao.", - "user_document_claims": { - "status": "Manica Parks lanado por memorando em Setembro de 2026; alegao de localizao em Selva/Vanduzi permanece por confirmar", - "activities": "agro-processamento, metalomecnica, combustveis, hotelaria e restaurao (enfoque alegado no documento)", - "assessment": "MozParks confirma iniciativa provincial e memorando, no uma instalao individual em Selva nem incio de construo/operao." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Mocuba ZFI", - "outcome": "reconcile_existing", - "prepared_codes": [ - "MOZ-MOCUBA-ZFI", - "MOZ-MOCUBA-ZEE" - ], - "source": "https://apiex.gov.mz/zif/mocuba-free-industrial-zone/", - "known": "APIEX confirma a ZFI de 50,58 ha e fase de mobilizao de recursos e operador. A lista de actividades do documento enfoque declarado, no autorizao.", - "pending": "Confirmar diploma primrio, limites e actividades; manter ZFI e ZEE de Mocuba separadas.", - "user_document_claims": { - "status": "mobilizao de recursos e operador", - "area": "50,58 ha segundo APIEX", - "activities": "agro-florestal, pescas, txteis, calcrio, pedra e minerais (enfoque alegado no documento)", - "assessment": "APIEX confirma 50,58 ha e mobilizao; confirmar actividades com fonte prpria antes de atribuir ao registo. Manter ZFI distinta da ZEE." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - }, - { - "name": "Revúbuè ZFI", - "outcome": "prepare_one_record_two_districts", - "prepared_codes": [ - "MOZ-REVUBUE-ZFI" - ], - "source": "https://apiex.gov.mz/revubue-free-industrial-zone/", - "known": "APIEX reporta a ZFI abrangendo Chiuta e Moatize, 4.456 ha, sem operador identificado.", - "pending": "Obter e validar texto primário do Decreto 41/2016 e as relações territoriais. Um único registo, duas localizações.", - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Mocambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - }, - "user_document_claims": { - "status": "sem operador; um projecto aprovado segundo APIEX", - "area": "4 456 ha segundo APIEX", - "activities": "industria ligada a recursos e transformacao (enfoque alegado no documento)", - "assessment": "APIEX informa um projecto aprovado e potenciais sectores; isto nao transforma o projecto em oportunidade publicada nem comprova actividades autorizadas." - } - }, - { - "name": "Minhewene ZFI", - "outcome": "prepare_province_precision_candidate", - "prepared_codes": [ - "MOZ-MINHEWENE-ZFI" - ], - "source": "https://apiex.gov.mz/zona-franca-industrial-de-minhewene/", - "known": "Pgina APIEX actualmente acessvel reporta Decreto 51/2011, Nampula/Cidade de Nacala, 350 ha e mobilizao de recursos/operador. O documento fornecido diz que a ficha no estava acessvel; esta afirmao diverge da consulta actual.", - "pending": "Validar Decreto 51/2011 e localizao cadastral exacta; pgina institucional no substitui fonte legal primria nem comprova operacionalizao actual.", - "user_document_claims": { - "status": "mobilizao de recursos e operador segundo pgina APIEX acessvel na verificao actual", - "area": "350 ha segundo APIEX", - "assessment": "A alegao do documento de que no havia ficha acessvel diverge da pgina institucional APIEX actualmente acessvel, que reporta Decreto 51/2011, Nampula/Cidade de Nacala, 350 ha e mobilizao de recursos/operador. Acessibilidade da pgina no substitui validao do diploma primrio ou do estado actual." - }, - "claim_source": { - "title": "Parques Industriais e Zonas Francas Industriais em Moçambique", - "source_type": "user_supplied_secondary_document", - "verified_as_primary": false - } - } - ], - "legislative_review": { - "decree_57_2023": "Texto oficial localizado; criação da ZFI de Topuito e 65 ha confirmados. Não implica que operador, actividade operacional ou terrenos disponíveis estejam confirmados.", - "decree_12_2026": "A página oficial consultada apresenta o sumário de aprovação do regulamento ZEE/ZFI e revogação do Decreto 75/99. O texto integral e regras transitórias/entrada em vigor ainda não foram revistos; não parametrizar simulador fiscal a partir do sumário." - }, - "official_checks": [ - { - "topic": "Beluluane", - "source": "https://mozparks.co.mz/", - "finding": "MozParks apresenta 700 ha prontos para arrendamento; APIEX reporta complexo de cerca de 780 ha, parque 600 ha, Mozal 180 ha, ZFI 55 ha. Escopos divergentes." - }, - { - "topic": "Topuito", - "source": "https://mozparks.co.mz/", - "finding": "MozParks apresenta 215 ha; notcia de 19-08-2026 diz SME Village comeou construo. No declara a totalidade da rea operacional/disponvel." - }, - { - "topic": "Munhava", - "source": "https://sdubeira.co.mz/project/parque-industrial-logistico/", - "finding": "A pgina da SDUBeira contm total 900 ha no resumo e 990 ha em destaque; piloto 50 ha. A faixa 44-60 ha no foi corroborada." - }, - { - "topic": "Manica Parks", - "source": "https://mozparks.co.mz/mozparks-launches-manica-parks-a-new-industrial-platform-for-manica-province/", - "finding": "Memorando e iniciativa provincial confirmados; Selva/Vanduzi no aparece como instalao individual confirmada." - }, - { - "topic": "Cabo Delgado Parks", - "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", - "finding": "MozParks identifica parques em desenvolvimento nos quatro distritos. Sectores do documento permanecem promocionais." - }, - { - "topic": "Minhewene", - "source": "https://apiex.gov.mz/zona-franca-industrial-de-minhewene/", - "finding": "Pgina APIEX est acessvel nesta consulta e reporta Decreto 51/2011, Nacala, 350 ha e mobilizao de recursos/operador; a alegao documental de indisponibilidade da ficha no corresponde ao acesso actual." - } - ] + "official_checks": [ + { + "topic": "Beluluane", + "source": "https://mozparks.co.mz/", + "finding": "MozParks apresenta 700 ha prontos para arrendamento; APIEX reporta complexo de cerca de 780 ha, parque 600 ha, Mozal 180 ha, ZFI 55 ha. Escopos divergentes." + }, + { + "topic": "Topuito", + "source": "https://mozparks.co.mz/", + "finding": "MozParks apresenta 215 ha; notcia de 19-08-2026 diz SME Village comeou construo. No declara a totalidade da rea operacional/disponvel." + }, + { + "topic": "Munhava", + "source": "https://sdubeira.co.mz/project/parque-industrial-logistico/", + "finding": "A pgina da SDUBeira contm total 900 ha no resumo e 990 ha em destaque; piloto 50 ha. A faixa 44-60 ha no foi corroborada." + }, + { + "topic": "Manica Parks", + "source": "https://mozparks.co.mz/mozparks-launches-manica-parks-a-new-industrial-platform-for-manica-province/", + "finding": "Memorando e iniciativa provincial confirmados; Selva/Vanduzi no aparece como instalao individual confirmada." + }, + { + "topic": "Cabo Delgado Parks", + "source": "https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/", + "finding": "MozParks identifica parques em desenvolvimento nos quatro distritos. Sectores do documento permanecem promocionais." + }, + { + "topic": "Minhewene", + "source": "https://apiex.gov.mz/zona-franca-industrial-de-minhewene/", + "finding": "Pgina APIEX est acessvel nesta consulta e reporta Decreto 51/2011, Nacala, 350 ha e mobilizao de recursos/operador; a alegao documental de indisponibilidade da ficha no corresponde ao acesso actual." + } + ] } diff --git a/database/data/investment-reference-options.json b/database/data/investment-reference-options.json index f6096843..4570403d 100644 --- a/database/data/investment-reference-options.json +++ b/database/data/investment-reference-options.json @@ -1,16 +1,125 @@ { - "need_category": {"labour_required":"Mão de obra necessária","labour_available":"Mão de obra disponível","infrastructure_existing":"Infraestruturas existentes","infrastructure_required":"Infraestruturas necessárias","education":"Educação e formação","support":"Apoios"}, - "infrastructure": {"energy":"Energia","water":"Água","sanitation":"Saneamento","telecom":"Telecomunicações","internet":"Internet","road":"Estrada","rail":"Ferrovia","port":"Porto","airport":"Aeroporto","storage":"Armazenagem","cold_chain":"Cadeia de frio","housing":"Habitação","health":"Saúde","education":"Educação","security":"Segurança","industrial":"Instalações industriais","office":"Escritórios","land":"Terrenos","logistics":"Serviços logísticos"}, - "education": {"preschool":"Educação pré-escolar","primary":"Ensino primário","secondary":"Ensino secundário","vocational":"Formação técnico-profissional","higher":"Ensino superior","research":"Investigação","innovation":"Centros de inovação","incubation":"Incubadoras","laboratory":"Laboratórios","edtech":"Tecnologia educativa","digital":"Plataformas digitais","connectivity":"Conectividade","buildings":"Infraestruturas escolares","equipment":"Equipamentos","library":"Bibliotecas","residence":"Alojamento","meals":"Alimentação escolar","transport":"Transporte","content":"Produção de conteúdos","teachers":"Formação de professores","skills":"Desenvolvimento de competências","continuing":"Formação contínua"}, - "labour": {"technical":"Pessoal técnico","professional":"Profissionais especializados","operational":"Pessoal operacional","management":"Gestão","training":"Formadores"}, - "qualification": {"primary":"Ensino primário","secondary":"Ensino secundário","technical":"Qualificação técnica","higher":"Ensino superior","postgraduate":"Pós-graduação"}, - "support": {"licence":"Licenças","incentives":"Incentivos","finance":"Financiamento","land":"Terra","logistics":"Logística","partners":"Parceiros","suppliers":"Fornecedores","recruitment":"Contratação","imports":"Importação","exports":"Exportação","installation":"Instalação","utilities":"Ligação a serviços públicos"}, - "priority": {"low":"Baixa","normal":"Normal","high":"Alta"}, - "unit": {"people":"Pessoas","units":"Unidades","mw":"MW","m3_day":"m³/dia","km":"km","hectares":"Hectares"}, - "participation": {"invest":"Investir","finance":"Financiar","supply":"Fornecer","operate":"Operar","partner":"Estabelecer parceria"}, - "partnership": {"direct":"Investimento directo","joint_venture":"Joint venture","ppp":"Parceria público-privada","supply":"Fornecimento","technical":"Cooperação técnica"}, - "investor_profile": {"individual":"Individual","company":"Empresa","fund":"Fundo","public":"Entidade pública"}, - "impact": {"employment":"Emprego","training":"Competências","local_supply":"Fornecimento local","environment":"Ambiente","exports":"Exportações"}, - "subsector": {}, - "need_status": {"declared":"Declarada","available":"Disponível segundo declaração","required":"Necessária"} + "need_category": { + "labour_required": "Mão de obra necessária", + "labour_available": "Mão de obra disponível", + "infrastructure_existing": "Infraestruturas existentes", + "infrastructure_required": "Infraestruturas necessárias", + "education": "Educação e formação", + "support": "Apoios" + }, + "infrastructure": { + "energy": "Energia", + "water": "Água", + "sanitation": "Saneamento", + "telecom": "Telecomunicações", + "internet": "Internet", + "road": "Estrada", + "rail": "Ferrovia", + "port": "Porto", + "airport": "Aeroporto", + "storage": "Armazenagem", + "cold_chain": "Cadeia de frio", + "housing": "Habitação", + "health": "Saúde", + "education": "Educação", + "security": "Segurança", + "industrial": "Instalações industriais", + "office": "Escritórios", + "land": "Terrenos", + "logistics": "Serviços logísticos" + }, + "education": { + "preschool": "Educação pré-escolar", + "primary": "Ensino primário", + "secondary": "Ensino secundário", + "vocational": "Formação técnico-profissional", + "higher": "Ensino superior", + "research": "Investigação", + "innovation": "Centros de inovação", + "incubation": "Incubadoras", + "laboratory": "Laboratórios", + "edtech": "Tecnologia educativa", + "digital": "Plataformas digitais", + "connectivity": "Conectividade", + "buildings": "Infraestruturas escolares", + "equipment": "Equipamentos", + "library": "Bibliotecas", + "residence": "Alojamento", + "meals": "Alimentação escolar", + "transport": "Transporte", + "content": "Produção de conteúdos", + "teachers": "Formação de professores", + "skills": "Desenvolvimento de competências", + "continuing": "Formação contínua" + }, + "labour": { + "technical": "Pessoal técnico", + "professional": "Profissionais especializados", + "operational": "Pessoal operacional", + "management": "Gestão", + "training": "Formadores" + }, + "qualification": { + "primary": "Ensino primário", + "secondary": "Ensino secundário", + "technical": "Qualificação técnica", + "higher": "Ensino superior", + "postgraduate": "Pós-graduação" + }, + "support": { + "licence": "Licenças", + "incentives": "Incentivos", + "finance": "Financiamento", + "land": "Terra", + "logistics": "Logística", + "partners": "Parceiros", + "suppliers": "Fornecedores", + "recruitment": "Contratação", + "imports": "Importação", + "exports": "Exportação", + "installation": "Instalação", + "utilities": "Ligação a serviços públicos" + }, + "priority": { "low": "Baixa", "normal": "Normal", "high": "Alta" }, + "unit": { + "people": "Pessoas", + "units": "Unidades", + "mw": "MW", + "m3_day": "m³/dia", + "km": "km", + "hectares": "Hectares" + }, + "participation": { + "invest": "Investir", + "finance": "Financiar", + "supply": "Fornecer", + "operate": "Operar", + "partner": "Estabelecer parceria" + }, + "partnership": { + "direct": "Investimento directo", + "joint_venture": "Joint venture", + "ppp": "Parceria público-privada", + "supply": "Fornecimento", + "technical": "Cooperação técnica" + }, + "investor_profile": { + "individual": "Individual", + "company": "Empresa", + "fund": "Fundo", + "public": "Entidade pública" + }, + "impact": { + "employment": "Emprego", + "training": "Competências", + "local_supply": "Fornecimento local", + "environment": "Ambiente", + "exports": "Exportações" + }, + "subsector": {}, + "need_status": { + "declared": "Declarada", + "available": "Disponível segundo declaração", + "required": "Necessária" + } } diff --git a/database/data/investment-zones-initial.json b/database/data/investment-zones-initial.json index 702daa93..a07e8e37 100644 --- a/database/data/investment-zones-initial.json +++ b/database/data/investment-zones-initial.json @@ -1,150 +1,321 @@ { - "source_prompt": "docs/investmoz/source-staging/zone-integration-2026-10-04/Pasted text.txt", - "consulted_at": "2026-10-04", - "records": [ - { - "code": "MOZ-BELULUANE-PARK", - "name": "Parque Industrial de Beluluane", - "name_en": "Beluluane Industrial Park", - "category": "industrial_park", - "physical_category": "industrial_park", - "legal_regime": "general", - "operational_status": "unknown", - "area_value": null, - "area_unit": null, - "area_components": [ - {"type":"total","value":780,"unit":"ha","status":"disputed","source_reference":"https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX reports the complex; scope and overlap need confirmation","source_date":null}, - {"type":"total","value":700,"unit":"ha","status":"disputed","source_reference":"Document cited in the integration prompt was not attached; claim retained as unresolved, not imported as verified data","source_date":null}, - {"type":"developed","value":600,"unit":"ha","status":"reported","source_reference":"https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX describes the industrial park component","source_date":null}, - {"type":"developed","value":180,"unit":"ha","status":"reported","source_reference":"https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX describes the Mozal site component; do not add as available land","source_date":null}, - {"type":"legal","value":55,"unit":"ha","status":"reported","source_reference":"https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — separate ZFI component; do not merge with park","source_date":null} - ], - "description": "A APIEX apresenta aproximadamente 780 ha para o conjunto referido como Parque Industrial de Beluluane–Zona Franca, incluindo componentes com perímetros distintos. O valor não representa área livre ou disponível. A fonte identifica historicamente Matola-Rio, Boane, Província de Maputo. Situação operacional e área disponível não confirmadas por esta referência.", - "description_en": "APIEX reports approximately 780 ha for the Beluluane Industrial Park–Free Zone complex, including distinct components. This is not an available-land figure. The source historically identifies Matola-Rio, Boane, Maputo Province. Operational status and available area are not confirmed by this reference.", - "aliases": ["PIB-ZF", "Parque Industrial de Beluluane–Zona Franca"], - "activities": [], - "historical_territory": {"name": "Matola-Rio", "district": "Boane", "province": "Maputo", "source_reference": "APIEX page; current territorial equivalence requires review"}, - "source": {"title": "Beluluane Industrial Park – Free Zone", "url": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", "diploma": "Resolução Interna do Conselho de Ministros n.º 15/99, de 12 de Outubro", "article": "Act of creation; article and current legal effect pending review", "valid_from": "1999-10-12", "competent_entity": "Conselho de Ministros / APIEX source page"}, - "territories": [{"name": "MATOLA RIO", "type": "admin_post", "scope_type": "administrative"}] - }, - { - "code": "MOZ-BELULUANE-ZFI", - "name": "Zona Franca Industrial de Beluluane", - "name_en": "Beluluane Industrial Free Zone", - "category": "zfi", - "physical_category": "industrial_free_zone", - "legal_regime": "zfi", - "operational_status": "unknown", - "area_value": 55, - "area_unit": "ha", - "description": "Componente ZFI distinta dentro do complexo Beluluane. A APIEX indica cerca de 55 ha. Este registo não atribui elegibilidade ou incentivos a qualquer empresa.", - "description_en": "A distinct IFZ component within the Beluluane complex. APIEX reports approximately 55 ha. This record does not establish eligibility or incentives for any company.", - "aliases": ["Beluluane IFZ"], - "activities": [], - "parent_code": "MOZ-BELULUANE-PARK", - "historical_territory": {"name": "Matola-Rio", "district": "Boane", "province": "Maputo", "source_reference": "APIEX page; current territorial equivalence requires review"}, - "source": {"title": "Beluluane Industrial Park – Free Zone", "url": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", "diploma": "Resolução Interna do Conselho de Ministros n.º 15/99, de 12 de Outubro", "article": "Act of creation; article and current legal effect pending review", "valid_from": "1999-10-12", "competent_entity": "Conselho de Ministros / APIEX source page"}, - "territories": [{"name": "MATOLA RIO", "type": "admin_post", "scope_type": "administrative"}] - }, - { - "code": "MOZ-CRUSSE-JAMALI-ZEE", - "name": "Zona de Estância de Turismo Integrado de Crusse e Jamali", - "name_en": "Crusse and Jamali Integrated Tourism Resort Area", - "category": "zee", - "physical_category": "integrated_tourism_area", - "legal_regime": "zee", - "operational_status": "unknown", - "area_value": 1750, - "area_unit": "ha", - "description": "Criada em regime de ZEE pelo Decreto n.º 47/2013, de 30 de Agosto. A fonte oficial indica 1.750 ha no Posto Administrativo de Matibane, Distrito de Mossuril, Nampula. A criação da zona não confirma que infra-estruturas ou oportunidades previstas estejam actualmente disponíveis.", - "description_en": "Created under the SEZ regime by Decree no. 47/2013 of 30 August. The official source states 1,750 ha in Matibane Administrative Post, Mossuril District, Nampula. Establishment of the zone does not confirm that planned infrastructure or opportunities are currently available.", - "aliases": ["Crusse-Jamali", "Crusse & Jamali"], - "activities": [{"name": "Turismo integrado", "status": "potential", "source_reference": "Decreto n.º 47/2013; tourism scope in the creation description"}], - "source": {"title": "Conselho de Ministros — BR n.º 70 de 30.08.13", "url": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-70-de-300813-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-571", "diploma": "Decreto n.º 47/2013, de 30 de Agosto", "article": "Act of creation; article reference pending review", "valid_from": "2013-08-30", "competent_entity": "Conselho de Ministros / Imprensa Nacional de Moçambique"}, - "territories": [{"name": "MATIBANE", "type": "admin_post", "scope_type": "legal_scope"}] - }, - { - "code": "MOZ-MANGA-MUNGASSA-ZEE", - "name": "Zona Económica Especial de Manga-Mungassa", - "name_en": "Manga-Mungassa Special Economic Zone", - "category": "zee", - "physical_category": "economic_zone", - "legal_regime": "zee", - "operational_status": "unknown", - "area_value": 217, - "area_unit": "ha", - "description": "A APIEX indica criação pelo Decreto n.º 22/2012, de 6 de Junho, localização na Cidade da Beira, Sofala, e área de 217 ha. A fonte refere sectores de potencial atracção; esses sectores não são tratados como actividades autorizadas nem como oportunidades cadastradas.", - "description_en": "APIEX reports establishment by Decree no. 22/2012 of 6 June, location in Beira City, Sofala, and an area of 217 ha. Sectors described as investment potential are not treated as authorized activities or registered opportunities.", - "aliases": ["Manga Mungassa ZEE"], - "activities": [ - {"name": "Indústria", "status": "potential", "source_reference": "APIEX Manga-Mungassa page; potential sectors"}, - {"name": "Logística e transporte", "status": "potential", "source_reference": "APIEX Manga-Mungassa page; potential sectors"}, - {"name": "Hotelaria e serviços", "status": "potential", "source_reference": "APIEX Manga-Mungassa page; potential sectors"} - ], - "source": {"title": "Manga-Mungassa Special Economic Zone – APIEX", "url": "https://apiex.gov.mz/zee/manga-mungassa-special-economic-zone/", "diploma": "Decreto n.º 22/2012, de 6 de Junho", "article": "Act of creation; article reference pending review", "valid_from": "2012-06-06", "competent_entity": "Conselho de Ministros / APIEX"}, - "territories": [{"name": "CIDADE DA BEIRA", "type": "district", "scope_type": "administrative"}] - }, - { - "code": "MOZ-MOCUBA-ZEE", - "name": "Zona Económica Especial de Mocuba", - "name_en": "Mocuba Special Economic Zone", - "category": "zee", - "physical_category": "economic_zone", - "legal_regime": "zee", - "operational_status": "unknown", - "area_value": 10727, - "area_unit": "km2", - "description": "Criada pelo Decreto n.º 28/2014, de 6 de Junho. O INM e a APIEX indicam que abrange todo o Distrito de Mocuba e o Posto Administrativo de Munhamade, no Distrito de Lugela, Zambézia. A APIEX indica 10.727 km²; a unidade e a abrangência são preservadas sem as reduzir à cidade de Mocuba.", - "description_en": "Created by Decree no. 28/2014 of 6 June. INM and APIEX state that it covers all of Mocuba District and Munhamade Administrative Post in Lugela District, Zambézia. APIEX reports 10,727 km²; the unit and scope are preserved without reducing it to Mocuba City.", - "aliases": ["Mocuba ZEE"], - "activities": [ - {"name": "Produção agrícola, pecuária, florestal e processamento", "status": "potential", "source_reference": "APIEX Mocuba SEZ page; stated attraction objectives"} - ], - "source": {"title": "BR n.º 46 de 06.06.14 — Decreto n.º 28/2014", "url": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-46-de-060614-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1259", "diploma": "Decreto n.º 28/2014, de 6 de Junho", "article": "Act of creation; article reference pending review", "valid_from": "2014-06-06", "competent_entity": "Conselho de Ministros / Imprensa Nacional de Moçambique"}, - "territories": [ - {"name": "MOCUBA", "type": "district", "scope_type": "legal_scope"}, - {"name": "MUNHAMADE", "type": "admin_post", "scope_type": "legal_scope"} - ] - }, - { - "code": "MOZ-MOCUBA-ZFI", - "name": "Zona Franca Industrial de Mocuba", - "name_en": "Mocuba Industrial Free Zone", - "category": "zfi", - "physical_category": "industrial_free_zone", - "legal_regime": "zfi", - "operational_status": "unknown", - "area_value": 50.58, - "area_unit": "ha", - "description": "Criada pelo Decreto n.º 29/2014, de 6 de Junho, e localizada na Cidade de Mocuba. A APIEX descreve esta ZFI como estando na fase de mobilização de recursos e do operador. É distinta da ZEE de Mocuba e não implica elegibilidade fiscal de empresas.", - "description_en": "Created by Decree no. 29/2014 of 6 June and located in Mocuba City. APIEX describes this IFZ as mobilizing resources and an operator. It is distinct from Mocuba SEZ and does not imply tax eligibility for companies.", - "aliases": ["Mocuba IFZ", "ZFI de Mocuba"], - "activities": [], - "source": {"title": "Mocuba IFZ – APIEX", "url": "https://apiex.gov.mz/zif/mocuba-free-industrial-zone/", "diploma": "Decreto n.º 29/2014, de 6 de Junho", "article": "Act of creation; article reference pending review", "valid_from": "2014-06-06", "competent_entity": "Conselho de Ministros / APIEX"}, - "territories": [{"name": "CIDADE DE MOCUBA", "type": "admin_post", "scope_type": "administrative"}] - }, - { - "code": "MOZ-NACALA-ZEE", - "name": "Zona Económica Especial de Nacala", - "name_en": "Nacala Special Economic Zone", - "category": "zee", - "physical_category": "economic_zone", - "legal_regime": "zee", - "operational_status": "unknown", - "area_value": 1539, - "area_unit": "km2", - "description": "A APIEX indica criação pelo Decreto n.º 76/2007, de 18 de Dezembro, abrangência dos distritos de Nacala e Nacala-à-Velha e área de 1.539 km². Não se confunde com o Corredor de Nacala, porto ou outras zonas francas. Os números de projectos e empregos publicados na página não são importados como actuais por não terem data de referência confirmada.", - "description_en": "APIEX reports establishment by Decree no. 76/2007 of 18 December, covering Nacala and Nacala-à-Velha districts, with an area of 1,539 km². It is distinct from the Nacala Corridor, port, and other free zones. Project and employment counts on the page are not imported as current because their reference date is not confirmed.", - "aliases": ["ZEEN", "Nacala SEZ"], - "activities": [ - {"name": "Logística e indústria", "status": "potential", "source_reference": "APIEX Nacala SEZ page; sectors identified as potential"}, - {"name": "Agro-processamento e indústria química", "status": "potential", "source_reference": "APIEX Nacala SEZ page; sectors identified as potential"} - ], - "source": {"title": "Nacala Special Economic Zone – APIEX", "url": "https://apiex.gov.mz/nacala-special-economic-zone/", "diploma": "Decreto n.º 76/2007, de 18 de Dezembro", "article": "Act of creation; article reference pending review", "valid_from": "2007-12-18", "competent_entity": "Conselho de Ministros / APIEX"}, - "territories": [ - {"name": "NACALA-PORTO", "type": "district", "scope_type": "legal_scope"}, - {"name": "NACALA-VELHA", "source_name": "Nacala-à-Velha", "type": "district", "scope_type": "legal_scope"} - ] - } - ] + "source_prompt": "docs/investmoz/source-staging/zone-integration-2026-10-04/Pasted text.txt", + "consulted_at": "2026-10-04", + "records": [ + { + "code": "MOZ-BELULUANE-PARK", + "name": "Parque Industrial de Beluluane", + "name_en": "Beluluane Industrial Park", + "category": "industrial_park", + "physical_category": "industrial_park", + "legal_regime": "general", + "operational_status": "unknown", + "area_value": null, + "area_unit": null, + "area_components": [ + { + "type": "total", + "value": 780, + "unit": "ha", + "status": "disputed", + "source_reference": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX reports the complex; scope and overlap need confirmation", + "source_date": null + }, + { + "type": "total", + "value": 700, + "unit": "ha", + "status": "disputed", + "source_reference": "Document cited in the integration prompt was not attached; claim retained as unresolved, not imported as verified data", + "source_date": null + }, + { + "type": "developed", + "value": 600, + "unit": "ha", + "status": "reported", + "source_reference": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX describes the industrial park component", + "source_date": null + }, + { + "type": "developed", + "value": 180, + "unit": "ha", + "status": "reported", + "source_reference": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — APIEX describes the Mozal site component; do not add as available land", + "source_date": null + }, + { + "type": "legal", + "value": 55, + "unit": "ha", + "status": "reported", + "source_reference": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/ — separate ZFI component; do not merge with park", + "source_date": null + } + ], + "description": "A APIEX apresenta aproximadamente 780 ha para o conjunto referido como Parque Industrial de Beluluane–Zona Franca, incluindo componentes com perímetros distintos. O valor não representa área livre ou disponível. A fonte identifica historicamente Matola-Rio, Boane, Província de Maputo. Situação operacional e área disponível não confirmadas por esta referência.", + "description_en": "APIEX reports approximately 780 ha for the Beluluane Industrial Park–Free Zone complex, including distinct components. This is not an available-land figure. The source historically identifies Matola-Rio, Boane, Maputo Province. Operational status and available area are not confirmed by this reference.", + "aliases": ["PIB-ZF", "Parque Industrial de Beluluane–Zona Franca"], + "activities": [], + "historical_territory": { + "name": "Matola-Rio", + "district": "Boane", + "province": "Maputo", + "source_reference": "APIEX page; current territorial equivalence requires review" + }, + "source": { + "title": "Beluluane Industrial Park – Free Zone", + "url": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", + "diploma": "Resolução Interna do Conselho de Ministros n.º 15/99, de 12 de Outubro", + "article": "Act of creation; article and current legal effect pending review", + "valid_from": "1999-10-12", + "competent_entity": "Conselho de Ministros / APIEX source page" + }, + "territories": [ + { + "name": "MATOLA RIO", + "type": "admin_post", + "scope_type": "administrative" + } + ] + }, + { + "code": "MOZ-BELULUANE-ZFI", + "name": "Zona Franca Industrial de Beluluane", + "name_en": "Beluluane Industrial Free Zone", + "category": "zfi", + "physical_category": "industrial_free_zone", + "legal_regime": "zfi", + "operational_status": "unknown", + "area_value": 55, + "area_unit": "ha", + "description": "Componente ZFI distinta dentro do complexo Beluluane. A APIEX indica cerca de 55 ha. Este registo não atribui elegibilidade ou incentivos a qualquer empresa.", + "description_en": "A distinct IFZ component within the Beluluane complex. APIEX reports approximately 55 ha. This record does not establish eligibility or incentives for any company.", + "aliases": ["Beluluane IFZ"], + "activities": [], + "parent_code": "MOZ-BELULUANE-PARK", + "historical_territory": { + "name": "Matola-Rio", + "district": "Boane", + "province": "Maputo", + "source_reference": "APIEX page; current territorial equivalence requires review" + }, + "source": { + "title": "Beluluane Industrial Park – Free Zone", + "url": "https://apiex.gov.mz/beluluane-industrial-park-free-zone/", + "diploma": "Resolução Interna do Conselho de Ministros n.º 15/99, de 12 de Outubro", + "article": "Act of creation; article and current legal effect pending review", + "valid_from": "1999-10-12", + "competent_entity": "Conselho de Ministros / APIEX source page" + }, + "territories": [ + { + "name": "MATOLA RIO", + "type": "admin_post", + "scope_type": "administrative" + } + ] + }, + { + "code": "MOZ-CRUSSE-JAMALI-ZEE", + "name": "Zona de Estância de Turismo Integrado de Crusse e Jamali", + "name_en": "Crusse and Jamali Integrated Tourism Resort Area", + "category": "zee", + "physical_category": "integrated_tourism_area", + "legal_regime": "zee", + "operational_status": "unknown", + "area_value": 1750, + "area_unit": "ha", + "description": "Criada em regime de ZEE pelo Decreto n.º 47/2013, de 30 de Agosto. A fonte oficial indica 1.750 ha no Posto Administrativo de Matibane, Distrito de Mossuril, Nampula. A criação da zona não confirma que infra-estruturas ou oportunidades previstas estejam actualmente disponíveis.", + "description_en": "Created under the SEZ regime by Decree no. 47/2013 of 30 August. The official source states 1,750 ha in Matibane Administrative Post, Mossuril District, Nampula. Establishment of the zone does not confirm that planned infrastructure or opportunities are currently available.", + "aliases": ["Crusse-Jamali", "Crusse & Jamali"], + "activities": [ + { + "name": "Turismo integrado", + "status": "potential", + "source_reference": "Decreto n.º 47/2013; tourism scope in the creation description" + } + ], + "source": { + "title": "Conselho de Ministros — BR n.º 70 de 30.08.13", + "url": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-70-de-300813-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-571", + "diploma": "Decreto n.º 47/2013, de 30 de Agosto", + "article": "Act of creation; article reference pending review", + "valid_from": "2013-08-30", + "competent_entity": "Conselho de Ministros / Imprensa Nacional de Moçambique" + }, + "territories": [ + { + "name": "MATIBANE", + "type": "admin_post", + "scope_type": "legal_scope" + } + ] + }, + { + "code": "MOZ-MANGA-MUNGASSA-ZEE", + "name": "Zona Económica Especial de Manga-Mungassa", + "name_en": "Manga-Mungassa Special Economic Zone", + "category": "zee", + "physical_category": "economic_zone", + "legal_regime": "zee", + "operational_status": "unknown", + "area_value": 217, + "area_unit": "ha", + "description": "A APIEX indica criação pelo Decreto n.º 22/2012, de 6 de Junho, localização na Cidade da Beira, Sofala, e área de 217 ha. A fonte refere sectores de potencial atracção; esses sectores não são tratados como actividades autorizadas nem como oportunidades cadastradas.", + "description_en": "APIEX reports establishment by Decree no. 22/2012 of 6 June, location in Beira City, Sofala, and an area of 217 ha. Sectors described as investment potential are not treated as authorized activities or registered opportunities.", + "aliases": ["Manga Mungassa ZEE"], + "activities": [ + { + "name": "Indústria", + "status": "potential", + "source_reference": "APIEX Manga-Mungassa page; potential sectors" + }, + { + "name": "Logística e transporte", + "status": "potential", + "source_reference": "APIEX Manga-Mungassa page; potential sectors" + }, + { + "name": "Hotelaria e serviços", + "status": "potential", + "source_reference": "APIEX Manga-Mungassa page; potential sectors" + } + ], + "source": { + "title": "Manga-Mungassa Special Economic Zone – APIEX", + "url": "https://apiex.gov.mz/zee/manga-mungassa-special-economic-zone/", + "diploma": "Decreto n.º 22/2012, de 6 de Junho", + "article": "Act of creation; article reference pending review", + "valid_from": "2012-06-06", + "competent_entity": "Conselho de Ministros / APIEX" + }, + "territories": [ + { + "name": "CIDADE DA BEIRA", + "type": "district", + "scope_type": "administrative" + } + ] + }, + { + "code": "MOZ-MOCUBA-ZEE", + "name": "Zona Económica Especial de Mocuba", + "name_en": "Mocuba Special Economic Zone", + "category": "zee", + "physical_category": "economic_zone", + "legal_regime": "zee", + "operational_status": "unknown", + "area_value": 10727, + "area_unit": "km2", + "description": "Criada pelo Decreto n.º 28/2014, de 6 de Junho. O INM e a APIEX indicam que abrange todo o Distrito de Mocuba e o Posto Administrativo de Munhamade, no Distrito de Lugela, Zambézia. A APIEX indica 10.727 km²; a unidade e a abrangência são preservadas sem as reduzir à cidade de Mocuba.", + "description_en": "Created by Decree no. 28/2014 of 6 June. INM and APIEX state that it covers all of Mocuba District and Munhamade Administrative Post in Lugela District, Zambézia. APIEX reports 10,727 km²; the unit and scope are preserved without reducing it to Mocuba City.", + "aliases": ["Mocuba ZEE"], + "activities": [ + { + "name": "Produção agrícola, pecuária, florestal e processamento", + "status": "potential", + "source_reference": "APIEX Mocuba SEZ page; stated attraction objectives" + } + ], + "source": { + "title": "BR n.º 46 de 06.06.14 — Decreto n.º 28/2014", + "url": "https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-46-de-060614-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1259", + "diploma": "Decreto n.º 28/2014, de 6 de Junho", + "article": "Act of creation; article reference pending review", + "valid_from": "2014-06-06", + "competent_entity": "Conselho de Ministros / Imprensa Nacional de Moçambique" + }, + "territories": [ + { + "name": "MOCUBA", + "type": "district", + "scope_type": "legal_scope" + }, + { + "name": "MUNHAMADE", + "type": "admin_post", + "scope_type": "legal_scope" + } + ] + }, + { + "code": "MOZ-MOCUBA-ZFI", + "name": "Zona Franca Industrial de Mocuba", + "name_en": "Mocuba Industrial Free Zone", + "category": "zfi", + "physical_category": "industrial_free_zone", + "legal_regime": "zfi", + "operational_status": "unknown", + "area_value": 50.58, + "area_unit": "ha", + "description": "Criada pelo Decreto n.º 29/2014, de 6 de Junho, e localizada na Cidade de Mocuba. A APIEX descreve esta ZFI como estando na fase de mobilização de recursos e do operador. É distinta da ZEE de Mocuba e não implica elegibilidade fiscal de empresas.", + "description_en": "Created by Decree no. 29/2014 of 6 June and located in Mocuba City. APIEX describes this IFZ as mobilizing resources and an operator. It is distinct from Mocuba SEZ and does not imply tax eligibility for companies.", + "aliases": ["Mocuba IFZ", "ZFI de Mocuba"], + "activities": [], + "source": { + "title": "Mocuba IFZ – APIEX", + "url": "https://apiex.gov.mz/zif/mocuba-free-industrial-zone/", + "diploma": "Decreto n.º 29/2014, de 6 de Junho", + "article": "Act of creation; article reference pending review", + "valid_from": "2014-06-06", + "competent_entity": "Conselho de Ministros / APIEX" + }, + "territories": [ + { + "name": "CIDADE DE MOCUBA", + "type": "admin_post", + "scope_type": "administrative" + } + ] + }, + { + "code": "MOZ-NACALA-ZEE", + "name": "Zona Económica Especial de Nacala", + "name_en": "Nacala Special Economic Zone", + "category": "zee", + "physical_category": "economic_zone", + "legal_regime": "zee", + "operational_status": "unknown", + "area_value": 1539, + "area_unit": "km2", + "description": "A APIEX indica criação pelo Decreto n.º 76/2007, de 18 de Dezembro, abrangência dos distritos de Nacala e Nacala-à-Velha e área de 1.539 km². Não se confunde com o Corredor de Nacala, porto ou outras zonas francas. Os números de projectos e empregos publicados na página não são importados como actuais por não terem data de referência confirmada.", + "description_en": "APIEX reports establishment by Decree no. 76/2007 of 18 December, covering Nacala and Nacala-à-Velha districts, with an area of 1,539 km². It is distinct from the Nacala Corridor, port, and other free zones. Project and employment counts on the page are not imported as current because their reference date is not confirmed.", + "aliases": ["ZEEN", "Nacala SEZ"], + "activities": [ + { + "name": "Logística e indústria", + "status": "potential", + "source_reference": "APIEX Nacala SEZ page; sectors identified as potential" + }, + { + "name": "Agro-processamento e indústria química", + "status": "potential", + "source_reference": "APIEX Nacala SEZ page; sectors identified as potential" + } + ], + "source": { + "title": "Nacala Special Economic Zone – APIEX", + "url": "https://apiex.gov.mz/nacala-special-economic-zone/", + "diploma": "Decreto n.º 76/2007, de 18 de Dezembro", + "article": "Act of creation; article reference pending review", + "valid_from": "2007-12-18", + "competent_entity": "Conselho de Ministros / APIEX" + }, + "territories": [ + { + "name": "NACALA-PORTO", + "type": "district", + "scope_type": "legal_scope" + }, + { + "name": "NACALA-VELHA", + "source_name": "Nacala-à-Velha", + "type": "district", + "scope_type": "legal_scope" + } + ] + } + ] } diff --git a/database/migrations/2026_09_28_160000_investment_primary_territory.php b/database/migrations/2026_09_28_160000_investment_primary_territory.php index 2becdc84..9ecf8516 100644 --- a/database/migrations/2026_09_28_160000_investment_primary_territory.php +++ b/database/migrations/2026_09_28_160000_investment_primary_territory.php @@ -31,7 +31,7 @@ return new class extends Migration $t->dropConstrainedForeignId('primary_territory_id'); $t->dropColumn('territory_version'); }); - + } } } diff --git a/database/migrations/2026_10_04_120000_add_simulation_draft_continuity.php b/database/migrations/2026_10_04_120000_add_simulation_draft_continuity.php index 04309be9..ba32beb2 100644 --- a/database/migrations/2026_10_04_120000_add_simulation_draft_continuity.php +++ b/database/migrations/2026_10_04_120000_add_simulation_draft_continuity.php @@ -21,7 +21,7 @@ return new class extends Migration ]); DB::statement('DROP INDEX IF EXISTS simulation_form_drafts_owner_active_unique'); DB::statement('CREATE INDEX simulation_form_drafts_owner_active_idx ON public.simulation_form_drafts(owner_user_id, updated_at DESC) WHERE owner_user_id IS NOT NULL AND deleted_at IS NULL'); - DB::statement("CREATE UNIQUE INDEX simulation_form_drafts_claimed_token_unique ON public.simulation_form_drafts(claimed_token_hash) WHERE claimed_token_hash IS NOT NULL"); + DB::statement('CREATE UNIQUE INDEX simulation_form_drafts_claimed_token_unique ON public.simulation_form_drafts(claimed_token_hash) WHERE claimed_token_hash IS NOT NULL'); Schema::create('simulation_draft_business_links', function ($table): void { $table->uuid('id')->primary(); diff --git a/database/migrations/2026_10_04_130000_extend_investment_zone_records.php b/database/migrations/2026_10_04_130000_extend_investment_zone_records.php index fd2fa860..e2c01d3a 100644 --- a/database/migrations/2026_10_04_130000_extend_investment_zone_records.php +++ b/database/migrations/2026_10_04_130000_extend_investment_zone_records.php @@ -26,7 +26,7 @@ return new class extends Migration DB::statement("ALTER TABLE investment_zones ADD CONSTRAINT investment_zones_physical_category_check CHECK (physical_category IS NULL OR physical_category IN ('economic_zone','industrial_park','industrial_free_zone','integrated_tourism_area','other','unknown'))"); DB::statement("ALTER TABLE investment_zones ADD CONSTRAINT investment_zones_legal_regime_check CHECK (legal_regime IS NULL OR legal_regime IN ('zee','zfi','general','pending','not_applicable'))"); DB::statement("ALTER TABLE investment_zones ADD CONSTRAINT investment_zones_operational_status_check CHECK (operational_status IN ('operational','implementation','planned','suspended','unknown'))"); - DB::statement("ALTER TABLE investment_zones ADD CONSTRAINT investment_zones_area_check CHECK (area_value IS NULL OR area_value >= 0)"); + DB::statement('ALTER TABLE investment_zones ADD CONSTRAINT investment_zones_area_check CHECK (area_value IS NULL OR area_value >= 0)'); Schema::create('investment_zone_locations', function (Blueprint $table): void { $table->uuid('id')->primary(); @@ -65,7 +65,7 @@ return new class extends Migration DB::statement('ALTER TABLE investment_zones DROP CONSTRAINT IF EXISTS investment_zones_area_check, DROP CONSTRAINT IF EXISTS investment_zones_operational_status_check, DROP CONSTRAINT IF EXISTS investment_zones_legal_regime_check, DROP CONSTRAINT IF EXISTS investment_zones_physical_category_check'); Schema::table('investment_zones', function (Blueprint $table): void { $table->dropConstrainedForeignId('parent_zone_id'); - $table->dropColumn(['physical_category','legal_regime','operational_status','area_value','area_unit','aliases','activities','resources','historical_territory','data_as_of','sources_consulted_at']); + $table->dropColumn(['physical_category', 'legal_regime', 'operational_status', 'area_value', 'area_unit', 'aliases', 'activities', 'resources', 'historical_territory', 'data_as_of', 'sources_consulted_at']); }); } }; diff --git a/database/migrations/2026_10_05_120000_extend_portal_document_catalogue.php b/database/migrations/2026_10_05_120000_extend_portal_document_catalogue.php index d5baebe5..36f0edae 100644 --- a/database/migrations/2026_10_05_120000_extend_portal_document_catalogue.php +++ b/database/migrations/2026_10_05_120000_extend_portal_document_catalogue.php @@ -78,7 +78,7 @@ return new class extends Migration DB::statement("ALTER TABLE portal_resource_links ADD CONSTRAINT portal_resource_links_relationship_check CHECK (relationship IN ('applies_to','supports','version_of','translation_of','amends','repeals','implements','cites','related_to'))"); DB::statement("ALTER TABLE portal_resource_links ADD CONSTRAINT portal_resource_links_applicability_check CHECK (applicability IN ('required','conditional','support','optional_template','evidence'))"); DB::statement("ALTER TABLE portal_resource_links ADD CONSTRAINT portal_resource_links_basis_check CHECK ((applicability NOT IN ('required','conditional') OR length(trim(coalesce(basis_reference,''))) >= 5) AND (applicability <> 'conditional' OR length(trim(coalesce(condition_text,''))) >= 5))"); - DB::statement("ALTER TABLE portal_resources ADD CONSTRAINT portal_resources_metadata_version_check CHECK (metadata_version > 0)"); + DB::statement('ALTER TABLE portal_resources ADD CONSTRAINT portal_resources_metadata_version_check CHECK (metadata_version > 0)'); DB::statement("ALTER TABLE portal_resource_revisions ADD CONSTRAINT portal_resource_revisions_change_type_check CHECK (change_type IN ('created','metadata_updated','published','unpublished'))"); } diff --git a/database/migrations/2026_10_06_140000_allow_unapproved_private_portal_resources.php b/database/migrations/2026_10_06_140000_allow_unapproved_private_portal_resources.php index 7ea8c778..0db17196 100644 --- a/database/migrations/2026_10_06_140000_allow_unapproved_private_portal_resources.php +++ b/database/migrations/2026_10_06_140000_allow_unapproved_private_portal_resources.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; + return new class extends Migration { public function up(): void diff --git a/database/migrations/2026_10_06_200000_add_document_requirement_evidence_links.php b/database/migrations/2026_10_06_200000_add_document_requirement_evidence_links.php index cbd45aee..752e2a45 100644 --- a/database/migrations/2026_10_06_200000_add_document_requirement_evidence_links.php +++ b/database/migrations/2026_10_06_200000_add_document_requirement_evidence_links.php @@ -12,7 +12,7 @@ return new class extends Migration Schema::table('portal_resource_links', function (Blueprint $table): void { $table->string('review_status', 16)->default('pending'); }); - DB::statement("ALTER TABLE portal_resource_links DROP CONSTRAINT portal_resource_links_target_type_check"); + DB::statement('ALTER TABLE portal_resource_links DROP CONSTRAINT portal_resource_links_target_type_check'); DB::statement("ALTER TABLE portal_resource_links ADD CONSTRAINT portal_resource_links_target_type_check CHECK (target_type IN ('theme','module','activity','stage','procedure','entity','territory','document','investment_type','legal_source','requirement'))"); DB::statement("ALTER TABLE portal_resource_links ADD CONSTRAINT portal_resource_links_review_status_check CHECK (review_status IN ('pending','confirmed'))"); DB::statement('ALTER TABLE portal_resource_revisions DROP CONSTRAINT portal_resource_revisions_change_type_check'); diff --git a/database/migrations/2026_10_07_190000_create_internal_virtual_desk.php b/database/migrations/2026_10_07_190000_create_internal_virtual_desk.php index 6a67c5c0..3ddf8294 100644 --- a/database/migrations/2026_10_07_190000_create_internal_virtual_desk.php +++ b/database/migrations/2026_10_07_190000_create_internal_virtual_desk.php @@ -19,60 +19,128 @@ return new class extends Migration { $this->guard(true); Schema::create('desk_service_versions', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->string('code'); $t->unsignedInteger('version'); $t->string('status')->default('draft'); - $t->jsonb('definition'); $t->jsonb('source'); $t->foreignId('author_id')->constrained('users')->restrictOnDelete(); - $t->foreignId('published_by')->nullable()->constrained('users')->restrictOnDelete(); $t->timestampTz('published_at')->nullable(); $t->timestampsTz(); $t->unique(['code', 'version']); + $t->uuid('id')->primary(); + $t->string('code'); + $t->unsignedInteger('version'); + $t->string('status')->default('draft'); + $t->jsonb('definition'); + $t->jsonb('source'); + $t->foreignId('author_id')->constrained('users')->restrictOnDelete(); + $t->foreignId('published_by')->nullable()->constrained('users')->restrictOnDelete(); + $t->timestampTz('published_at')->nullable(); + $t->timestampsTz(); + $t->unique(['code', 'version']); }); Schema::create('desk_cases', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignId('user_id')->constrained('users')->restrictOnDelete(); + $t->uuid('id')->primary(); + $t->foreignId('user_id')->constrained('users')->restrictOnDelete(); $t->foreignUuid('organization_id')->nullable()->constrained('organizations')->restrictOnDelete(); $t->foreignUuid('company_id')->nullable()->constrained('investment_company_declarations')->restrictOnDelete(); $t->foreignUuid('submission_id')->nullable()->constrained('investment_submissions')->restrictOnDelete(); - $t->uuid('simulation_id')->nullable(); $t->string('title'); $t->uuid('client_key'); $t->unsignedInteger('version')->default(1); - $t->text('payload'); $t->jsonb('workflow'); $t->timestampTz('closed_at')->nullable(); $t->timestampsTz(); $t->unique(['user_id', 'client_key']); + $t->uuid('simulation_id')->nullable(); + $t->string('title'); + $t->uuid('client_key'); + $t->unsignedInteger('version')->default(1); + $t->text('payload'); + $t->jsonb('workflow'); + $t->timestampTz('closed_at')->nullable(); + $t->timestampsTz(); + $t->unique(['user_id', 'client_key']); }); Schema::create('desk_requests', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); + $t->uuid('id')->primary(); + $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); $t->foreignUuid('service_version_id')->constrained('desk_service_versions')->restrictOnDelete(); - $t->foreignId('analyst_id')->nullable()->constrained('users')->restrictOnDelete(); $t->string('status')->default('draft'); - $t->unsignedInteger('version')->default(1); $t->uuid('client_key'); $t->text('payload'); $t->string('protocol')->nullable()->unique(); - $t->text('submitted_snapshot')->nullable(); $t->jsonb('editable_fields')->nullable(); $t->timestampTz('submitted_at')->nullable(); - $t->timestampTz('due_at')->nullable(); $t->timestampTz('closed_at')->nullable(); $t->timestampsTz(); $t->unique(['case_id', 'client_key']); + $t->foreignId('analyst_id')->nullable()->constrained('users')->restrictOnDelete(); + $t->string('status')->default('draft'); + $t->unsignedInteger('version')->default(1); + $t->uuid('client_key'); + $t->text('payload'); + $t->string('protocol')->nullable()->unique(); + $t->text('submitted_snapshot')->nullable(); + $t->jsonb('editable_fields')->nullable(); + $t->timestampTz('submitted_at')->nullable(); + $t->timestampTz('due_at')->nullable(); + $t->timestampTz('closed_at')->nullable(); + $t->timestampsTz(); + $t->unique(['case_id', 'client_key']); }); Schema::create('desk_documents', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); - $t->foreignUuid('document_id')->constrained('documents')->restrictOnDelete(); $t->foreignUuid('request_id')->nullable()->constrained('desk_requests')->restrictOnDelete(); - $t->string('category'); $t->string('stage')->nullable(); $t->date('valid_until')->nullable(); $t->text('source_reference'); - $t->foreignId('linked_by')->constrained('users')->restrictOnDelete(); $t->timestampTz('created_at')->useCurrent(); $t->unique(['case_id', 'document_id']); + $t->uuid('id')->primary(); + $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); + $t->foreignUuid('document_id')->constrained('documents')->restrictOnDelete(); + $t->foreignUuid('request_id')->nullable()->constrained('desk_requests')->restrictOnDelete(); + $t->string('category'); + $t->string('stage')->nullable(); + $t->date('valid_until')->nullable(); + $t->text('source_reference'); + $t->foreignId('linked_by')->constrained('users')->restrictOnDelete(); + $t->timestampTz('created_at')->useCurrent(); + $t->unique(['case_id', 'document_id']); }); Schema::create('desk_events', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); - $t->foreignUuid('request_id')->nullable()->constrained('desk_requests')->restrictOnDelete(); $t->foreignId('actor_id')->constrained('users')->restrictOnDelete(); - $t->string('action'); $t->unsignedInteger('version'); $t->text('payload'); $t->timestampTz('created_at')->useCurrent(); + $t->uuid('id')->primary(); + $t->foreignUuid('case_id')->constrained('desk_cases')->restrictOnDelete(); + $t->foreignUuid('request_id')->nullable()->constrained('desk_requests')->restrictOnDelete(); + $t->foreignId('actor_id')->constrained('users')->restrictOnDelete(); + $t->string('action'); + $t->unsignedInteger('version'); + $t->text('payload'); + $t->timestampTz('created_at')->useCurrent(); }); Schema::create('desk_revisions', function (Blueprint $t) { - $t->id(); $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); $t->unsignedInteger('version'); - $t->text('snapshot'); $t->timestampTz('created_at')->useCurrent(); $t->unique(['request_id', 'version']); + $t->id(); + $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); + $t->unsignedInteger('version'); + $t->text('snapshot'); + $t->timestampTz('created_at')->useCurrent(); + $t->unique(['request_id', 'version']); }); Schema::create('desk_messages', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); - $t->foreignId('actor_id')->constrained('users')->restrictOnDelete(); $t->uuid('client_key'); $t->text('body'); $t->timestampTz('created_at')->useCurrent(); + $t->uuid('id')->primary(); + $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); + $t->foreignId('actor_id')->constrained('users')->restrictOnDelete(); + $t->uuid('client_key'); + $t->text('body'); + $t->timestampTz('created_at')->useCurrent(); $t->unique(['request_id', 'actor_id', 'client_key']); }); Schema::create('desk_notifications', function (Blueprint $t) { - $t->id(); $t->foreignUuid('event_id')->constrained('desk_events')->restrictOnDelete(); $t->foreignId('user_id')->constrained('users')->restrictOnDelete(); - $t->string('title'); $t->string('href'); $t->timestampTz('read_at')->nullable(); $t->unique(['event_id', 'user_id']); + $t->id(); + $t->foreignUuid('event_id')->constrained('desk_events')->restrictOnDelete(); + $t->foreignId('user_id')->constrained('users')->restrictOnDelete(); + $t->string('title'); + $t->string('href'); + $t->timestampTz('read_at')->nullable(); + $t->unique(['event_id', 'user_id']); }); Schema::create('desk_institutional_links', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->string('stage'); $t->string('entity'); $t->string('purpose'); $t->text('url'); $t->string('contact')->nullable(); - $t->string('status')->default('pending'); $t->unsignedInteger('version')->default(1); $t->text('source_reference'); - $t->timestampTz('verified_at')->nullable(); $t->foreignId('verified_by')->nullable()->constrained('users')->restrictOnDelete(); $t->timestampsTz(); + $t->uuid('id')->primary(); + $t->string('stage'); + $t->string('entity'); + $t->string('purpose'); + $t->text('url'); + $t->string('contact')->nullable(); + $t->string('status')->default('pending'); + $t->unsignedInteger('version')->default(1); + $t->text('source_reference'); + $t->timestampTz('verified_at')->nullable(); + $t->foreignId('verified_by')->nullable()->constrained('users')->restrictOnDelete(); + $t->timestampsTz(); }); Schema::create('desk_payments', function (Blueprint $t) { - $t->uuid('id')->primary(); $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); - $t->string('reference')->unique(); $t->decimal('amount', 18, 2); $t->string('currency', 3); $t->text('source_reference'); - $t->string('status')->default('pending'); $t->foreignUuid('document_id')->nullable()->constrained('documents')->restrictOnDelete(); - $t->foreignId('verified_by')->nullable()->constrained('users')->restrictOnDelete(); $t->text('reason')->nullable(); $t->unsignedInteger('version')->default(1); $t->timestampsTz(); + $t->uuid('id')->primary(); + $t->foreignUuid('request_id')->constrained('desk_requests')->restrictOnDelete(); + $t->string('reference')->unique(); + $t->decimal('amount', 18, 2); + $t->string('currency', 3); + $t->text('source_reference'); + $t->string('status')->default('pending'); + $t->foreignUuid('document_id')->nullable()->constrained('documents')->restrictOnDelete(); + $t->foreignId('verified_by')->nullable()->constrained('users')->restrictOnDelete(); + $t->text('reason')->nullable(); + $t->unsignedInteger('version')->default(1); + $t->timestampsTz(); }); DB::statement("ALTER TABLE desk_requests ADD CONSTRAINT desk_request_status CHECK (status IN ('draft','submitted','reviewing','returned','opinion','accepted','rejected','closed','cancelled'))"); DB::statement("ALTER TABLE desk_requests ADD CONSTRAINT desk_request_receipt CHECK (status IN ('draft','cancelled') OR (protocol IS NOT NULL AND submitted_snapshot IS NOT NULL AND submitted_at IS NOT NULL))"); @@ -83,8 +151,12 @@ return new class extends Migration { $this->guard(); foreach (['desk_cases', 'desk_service_versions', 'desk_institutional_links'] as $table) { - if (DB::table($table)->exists()) throw new RuntimeException('Preserve virtual desk data before rollback.'); + if (DB::table($table)->exists()) { + throw new RuntimeException('Preserve virtual desk data before rollback.'); + } + } + foreach (['desk_payments', 'desk_institutional_links', 'desk_notifications', 'desk_messages', 'desk_revisions', 'desk_events', 'desk_documents', 'desk_requests', 'desk_cases', 'desk_service_versions'] as $table) { + Schema::drop($table); } - foreach (['desk_payments', 'desk_institutional_links', 'desk_notifications', 'desk_messages', 'desk_revisions', 'desk_events', 'desk_documents', 'desk_requests', 'desk_cases', 'desk_service_versions'] as $table) Schema::drop($table); } }; diff --git a/database/migrations/2026_10_08_120000_extend_desk_investor_guidance.php b/database/migrations/2026_10_08_120000_extend_desk_investor_guidance.php new file mode 100644 index 00000000..986fce30 --- /dev/null +++ b/database/migrations/2026_10_08_120000_extend_desk_investor_guidance.php @@ -0,0 +1,63 @@ +environment('testing') && str_ends_with($identity->database, '_test') && (int) $identity->port === 55441) + && ! config('virtual-desk.installation_authorized')) { + throw new RuntimeException('Guidance requires isolated QA or authenticated installation with verified restore.'); + } + } + + public function up(): void + { + $this->guard(); + DB::statement('ALTER TABLE desk_events ADD COLUMN event_order bigint GENERATED ALWAYS AS IDENTITY'); + DB::statement('CREATE UNIQUE INDEX desk_event_order_unique ON desk_events(event_order)'); + Schema::table('desk_cases', function (Blueprint $t) { + $t->foreignUuid('investor_id')->nullable()->constrained('investors')->restrictOnDelete(); + $t->foreignUuid('project_id')->nullable()->constrained('projects')->restrictOnDelete(); + $t->foreignUuid('journey_id')->nullable()->constrained('investor_journeys')->restrictOnDelete(); + $t->foreignId('interest_request_id')->nullable()->constrained('interest_requests')->restrictOnDelete(); + }); + Schema::table('tasks', function (Blueprint $t) { + $t->foreignUuid('desk_case_id')->nullable()->constrained('desk_cases')->restrictOnDelete(); + $t->foreignUuid('desk_request_id')->nullable()->constrained('desk_requests')->restrictOnDelete(); + $t->string('guidance_key', 100)->nullable(); + $t->foreignId('accepted_by')->nullable()->constrained('users')->restrictOnDelete(); + $t->timestampTz('accepted_at')->nullable(); + $t->string('external_sync_status')->nullable(); + $t->unique(['desk_case_id', 'guidance_key']); + }); + DB::statement("ALTER TABLE tasks ADD CONSTRAINT guidance_task_acceptance CHECK (desk_case_id IS NULL OR (accepted_by IS NOT NULL AND accepted_at IS NOT NULL AND guidance_key IS NOT NULL AND data_classification = 'confidential' AND external_sync_status = 'pending_authorization'))"); + } + + public function down(): void + { + $this->guard(); + if (DB::table('tasks')->whereNotNull('desk_case_id')->exists() || DB::table('desk_cases')->whereNotNull('investor_id')->exists()) { + throw new RuntimeException('Preserve guidance associations and accepted tasks before rollback.'); + } + DB::statement('ALTER TABLE tasks DROP CONSTRAINT guidance_task_acceptance'); + DB::statement('ALTER TABLE desk_events DROP COLUMN event_order'); + Schema::table('tasks', function (Blueprint $t) { + $t->dropUnique(['desk_case_id', 'guidance_key']); + foreach (['desk_case_id', 'desk_request_id', 'accepted_by'] as $column) { + $t->dropConstrainedForeignId($column); + } + $t->dropColumn(['guidance_key', 'accepted_at', 'external_sync_status']); + }); + Schema::table('desk_cases', function (Blueprint $t) { + foreach (['investor_id', 'project_id', 'journey_id', 'interest_request_id'] as $column) { + $t->dropConstrainedForeignId($column); + } + }); + } +}; diff --git a/deploy/docker/compose.yaml b/deploy/docker/compose.yaml index c05405dc..80e832a6 100644 --- a/deploy/docker/compose.yaml +++ b/deploy/docker/compose.yaml @@ -1,128 +1,132 @@ name: investmoz-development services: - app: - build: - context: ../../vendor/laravel/sail/runtimes/8.5 - args: - WWWGROUP: '${WWWGROUP:-1000}' - NODE_VERSION: '24' - POSTGRES_VERSION: '17' - image: investmoz-development-php85 - environment: &application - WWWUSER: '${WWWUSER:-1000}' - APP_ENV: local - APP_DEBUG: 'false' - APP_KEY: '${APP_KEY:?Generate the isolated development key first}' - APP_URL: 'http://localhost:${PORTAL_PORT:-8088}' - DB_CONNECTION: pgsql - DB_HOST: postgres - DB_PORT: '5432' - DB_DATABASE: investmoz_development - DB_USERNAME: investmoz - DB_PASSWORD: '${DB_PASSWORD:?Generate a local database password first}' - DB_URL: '' - CACHE_STORE: database - QUEUE_CONNECTION: database - DB_QUEUE_RETRY_AFTER: '210' - SESSION_DRIVER: database - SESSION_SECURE_COOKIE: 'false' - MAIL_MAILER: array - PORTAL_NOTIFICATIONS_ENABLED: 'false' - LOG_CHANNEL: stderr - FILESYSTEM_DISK: local - MALWARE_SCANNER: clamd - CLAMD_HOST: clamav - CLAMD_PORT: '3310' - CLAMD_TIMEOUT_SECONDS: '120' - CLAMD_MAX_SIGNATURE_AGE_HOURS: '48' - CLAMD_MAX_FILE_BYTES: '20971520' - CLAMD_MAX_STREAM_BYTES: '26214400' - CLAMD_MAX_DECOMPRESSED_BYTES: '104857600' - CLAMD_MAX_ARCHIVE_ENTRIES: '1000' - volumes: - - ../..:/var/www/html - - ./runtime.env.example:/var/www/html/.env:ro - - application-storage:/var/www/html/storage - - application-cache:/var/www/html/bootstrap/cache - depends_on: - postgres: - condition: service_healthy - networks: - - default - - malware-private - healthcheck: - test: ['CMD', 'curl', '--fail', 'http://127.0.0.1/up'] - interval: 15s - timeout: 5s - retries: 5 - web: - image: nginx:stable-alpine - ports: - - '127.0.0.1:${PORTAL_PORT:-8088}:80' - volumes: - - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - depends_on: - app: - condition: service_healthy - queue: - image: investmoz-development-php85 - environment: - <<: *application - SUPERVISOR_PHP_COMMAND: '/usr/bin/php /var/www/html/artisan queue:work --sleep=3 --tries=3 --timeout=150' - volumes: - - ../..:/var/www/html - - ./runtime.env.example:/var/www/html/.env:ro - - application-storage:/var/www/html/storage - - application-cache:/var/www/html/bootstrap/cache - depends_on: - app: - condition: service_healthy - networks: - - default - - malware-private - postgres: - image: postgis/postgis:17-3.5 - environment: - POSTGRES_DB: investmoz_development - POSTGRES_USER: investmoz - POSTGRES_PASSWORD: '${DB_PASSWORD:?Generate a local database password first}' - volumes: - - database:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U investmoz -d investmoz_development'] - interval: 5s - timeout: 5s - retries: 10 - networks: - - default - clamav: - image: clamav/clamav:1.5.4 - environment: - CLAMAV_NO_MILTERD: 'true' - CLAMD_STARTUP_TIMEOUT: '1800' - FRESHCLAM_CHECKS: '12' - volumes: - - clamav-database:/var/lib/clamav - - ./clamd.conf:/etc/clamav/clamd.conf:ro - mem_limit: 4g - cpus: 2.0 - logging: - options: - max-size: '10m' - max-file: '3' - healthcheck: - test: ['CMD', 'clamdscan', '--ping=3'] - interval: 30s - timeout: 5s - retries: 3 - start_period: 30m - networks: - - malware-private + app: + build: + context: ../../vendor/laravel/sail/runtimes/8.5 + args: + WWWGROUP: '${WWWGROUP:-1000}' + NODE_VERSION: '24' + POSTGRES_VERSION: '17' + image: investmoz-development-php85 + environment: &application + WWWUSER: '${WWWUSER:-1000}' + APP_ENV: local + APP_DEBUG: 'false' + APP_KEY: '${APP_KEY:?Generate the isolated development key first}' + APP_URL: 'http://localhost:${PORTAL_PORT:-8088}' + DB_CONNECTION: pgsql + DB_HOST: postgres + DB_PORT: '5432' + DB_DATABASE: investmoz_development + DB_USERNAME: investmoz + DB_PASSWORD: '${DB_PASSWORD:?Generate a local database password first}' + DB_URL: '' + CACHE_STORE: database + QUEUE_CONNECTION: database + DB_QUEUE_RETRY_AFTER: '210' + SESSION_DRIVER: database + SESSION_SECURE_COOKIE: 'false' + MAIL_MAILER: array + PORTAL_NOTIFICATIONS_ENABLED: 'false' + LOG_CHANNEL: stderr + FILESYSTEM_DISK: local + MALWARE_SCANNER: clamd + CLAMD_HOST: clamav + CLAMD_PORT: '3310' + CLAMD_TIMEOUT_SECONDS: '120' + CLAMD_MAX_SIGNATURE_AGE_HOURS: '48' + CLAMD_MAX_FILE_BYTES: '20971520' + CLAMD_MAX_STREAM_BYTES: '26214400' + CLAMD_MAX_DECOMPRESSED_BYTES: '104857600' + CLAMD_MAX_ARCHIVE_ENTRIES: '1000' + volumes: + - ../..:/var/www/html + - ./runtime.env.example:/var/www/html/.env:ro + - application-storage:/var/www/html/storage + - application-cache:/var/www/html/bootstrap/cache + depends_on: + postgres: + condition: service_healthy + networks: + - default + - malware-private + healthcheck: + test: ['CMD', 'curl', '--fail', 'http://127.0.0.1/up'] + interval: 15s + timeout: 5s + retries: 5 + web: + image: nginx:stable-alpine + ports: + - '127.0.0.1:${PORTAL_PORT:-8088}:80' + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + app: + condition: service_healthy + queue: + image: investmoz-development-php85 + environment: + <<: *application + SUPERVISOR_PHP_COMMAND: '/usr/bin/php /var/www/html/artisan queue:work --sleep=3 --tries=3 --timeout=150' + volumes: + - ../..:/var/www/html + - ./runtime.env.example:/var/www/html/.env:ro + - application-storage:/var/www/html/storage + - application-cache:/var/www/html/bootstrap/cache + depends_on: + app: + condition: service_healthy + networks: + - default + - malware-private + postgres: + image: postgis/postgis:17-3.5 + environment: + POSTGRES_DB: investmoz_development + POSTGRES_USER: investmoz + POSTGRES_PASSWORD: '${DB_PASSWORD:?Generate a local database password first}' + volumes: + - database:/var/lib/postgresql/data + healthcheck: + test: + [ + 'CMD-SHELL', + 'pg_isready -U investmoz -d investmoz_development', + ] + interval: 5s + timeout: 5s + retries: 10 + networks: + - default + clamav: + image: clamav/clamav:1.5.4 + environment: + CLAMAV_NO_MILTERD: 'true' + CLAMD_STARTUP_TIMEOUT: '1800' + FRESHCLAM_CHECKS: '12' + volumes: + - clamav-database:/var/lib/clamav + - ./clamd.conf:/etc/clamav/clamd.conf:ro + mem_limit: 4g + cpus: 2.0 + logging: + options: + max-size: '10m' + max-file: '3' + healthcheck: + test: ['CMD', 'clamdscan', '--ping=3'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30m + networks: + - malware-private networks: - malware-private: - driver: bridge + malware-private: + driver: bridge volumes: - database: - application-storage: - application-cache: - clamav-database: + database: + application-storage: + application-cache: + clamav-database: diff --git a/docs/implementation/baseline.md b/docs/implementation/baseline.md index e3c30602..66e0cfbd 100644 --- a/docs/implementation/baseline.md +++ b/docs/implementation/baseline.md @@ -8,16 +8,16 @@ O repositório já tinha alterações funcionais locais no início da auditoria: ## Stack e versões confirmadas -| Componente | Baseline observado | Evidência | -| --- | --- | --- | -| Backend | Laravel 13.30.1 (`composer.lock`); `composer.json` exige PHP `^8.3` e Laravel `^13.17` | `composer.lock`, `composer.json` | -| PHP CLI | PHP 8.5.11; `php.ini` em `C:\Users\macbook\.config\investmoz\php-8.5.11\php.ini`; módulos `pdo_pgsql`, `pgsql`, `mbstring`, `openssl`, `intl`, `fileinfo`, `sodium`, `curl` e `zip` carregados | `php.exe -v`, `--ini`, `-m` | -| Composer | Composer 2.8.12; `composer.json` validado com o PHP 8.5.11 explícito | `composer.phar validate --no-check-publish` | -| Frontend | React 19.2.8, React DOM 19.2.8, Inertia React 3.7.0, TypeScript 5.9.3, Vite 8.2.2, Vite Plus 0.3.0, Leaflet 1.9.4 | `package-lock.json` | -| Node/npm | Node 24.20.0, npm 11.19.0 | `node --version`, `npm --version` | -| Dados | PostgreSQL 17.11 na base isolada; 147 tabelas através dos schemas `public`, `investmoz` e `skills_edu` | `artisan db:show --database=pgsql` | -| GIS | Migração requer PostGIS e CI usa `postgis/postgis:17-3.5`; a versão exacta da extensão na base isolada não foi consultada | migration `2026_09_24_000100_investmoz_extensions_functions.php`, `.github/workflows/tests.yml` | -| UI/SSR | Laravel + Inertia + React/Vite; não é Next.js | `composer.json`, `package.json`, `vite.config.ts`, `resources/js` | +| Componente | Baseline observado | Evidência | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| Backend | Laravel 13.30.1 (`composer.lock`); `composer.json` exige PHP `^8.3` e Laravel `^13.17` | `composer.lock`, `composer.json` | +| PHP CLI | PHP 8.5.11; `php.ini` em `C:\Users\macbook\.config\investmoz\php-8.5.11\php.ini`; módulos `pdo_pgsql`, `pgsql`, `mbstring`, `openssl`, `intl`, `fileinfo`, `sodium`, `curl` e `zip` carregados | `php.exe -v`, `--ini`, `-m` | +| Composer | Composer 2.8.12; `composer.json` validado com o PHP 8.5.11 explícito | `composer.phar validate --no-check-publish` | +| Frontend | React 19.2.8, React DOM 19.2.8, Inertia React 3.7.0, TypeScript 5.9.3, Vite 8.2.2, Vite Plus 0.3.0, Leaflet 1.9.4 | `package-lock.json` | +| Node/npm | Node 24.20.0, npm 11.19.0 | `node --version`, `npm --version` | +| Dados | PostgreSQL 17.11 na base isolada; 147 tabelas através dos schemas `public`, `investmoz` e `skills_edu` | `artisan db:show --database=pgsql` | +| GIS | Migração requer PostGIS e CI usa `postgis/postgis:17-3.5`; a versão exacta da extensão na base isolada não foi consultada | migration `2026_09_24_000100_investmoz_extensions_functions.php`, `.github/workflows/tests.yml` | +| UI/SSR | Laravel + Inertia + React/Vite; não é Next.js | `composer.json`, `package.json`, `vite.config.ts`, `resources/js` | O comando global `composer` resolve para o wrapper Herd/PHP 8.5.0. Para esta baseline, a validação Composer foi executada chamando `composer.phar` com o binário PHP 8.5.11 indicado pelo utilizador. @@ -79,29 +79,29 @@ Inventário local: 36 ficheiros de testes Feature e 5 Unit, mais testes Node. `p **Gate executado nesta auditoria:** -| Comando | Resultado | -| --- | --- | -| PHP 8.5.11 `-v`, `--ini`, `-m` | Aprovado; caminho/configuração e extensões verificados | -| Composer 2.8.12 `validate --no-check-publish`, invocado pelo PHP 8.5.11 | Aprovado: `composer.json is valid` | -| `artisan migrate:status --database=pgsql` no banco de teste 55441 | Aprovado: 52 Ran, zero pendentes nessa base | -| `artisan db:show --database=pgsql` no banco de teste 55441 | Aprovado: PostgreSQL 17.11; 147 tabelas | -| `npm.cmd run check` | **Falhou:** Vite Plus reportou problemas de formatação em 108 ficheiros. Não foi aplicado autofix. A execução parou aqui, conforme o gate pedido. | -| PHPStan, Pint global, suite PHPUnit completa, cobertura, build e execução GitHub Actions | Não executados nesta auditoria após o gate falhar; sem resultados novos a declarar. | +| Comando | Resultado | +| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| PHP 8.5.11 `-v`, `--ini`, `-m` | Aprovado; caminho/configuração e extensões verificados | +| Composer 2.8.12 `validate --no-check-publish`, invocado pelo PHP 8.5.11 | Aprovado: `composer.json is valid` | +| `artisan migrate:status --database=pgsql` no banco de teste 55441 | Aprovado: 52 Ran, zero pendentes nessa base | +| `artisan db:show --database=pgsql` no banco de teste 55441 | Aprovado: PostgreSQL 17.11; 147 tabelas | +| `npm.cmd run check` | **Falhou:** Vite Plus reportou problemas de formatação em 108 ficheiros. Não foi aplicado autofix. A execução parou aqui, conforme o gate pedido. | +| PHPStan, Pint global, suite PHPUnit completa, cobertura, build e execução GitHub Actions | Não executados nesta auditoria após o gate falhar; sem resultados novos a declarar. | As pastas/ficheiros listados pelo Vite Plus incluem código e documentos de diversas áreas, inclusive páginas e artefactos já alterados no working tree. Não foram modificados para preservar o escopo da Etapa 0. ## Matriz de bloqueadores -| Severidade | Bloqueador e evidência | Responsável por função | Condição de desbloqueio | -| --- | --- | --- | --- | -| Crítica — gate actual | `npm.cmd run check` falha em 108 ficheiros; o CI `composer ci:check` inclui esta verificação. | Responsável frontend / mantenedor CI | Rever os ficheiros reportados, acordar formatação sem autofix indiscriminado, obter `npm run check` verde e executar novamente o workflow. | -| Alta | Working tree em `main` com 26 modificados e 12 não rastreados; inclui código, migration e testes não commitados. | Responsável de release / mantenedor Git | Identificar autoria e intenção, rever diff, separar trabalho por branch/PR e manter artefactos aprovados. | -| Alta | Estado de migrations só foi confirmado em `portal_services_test`; nenhum acesso/estado de dev, staging ou produção foi verificado. | DBA / responsável de release | Consultar cada ambiente com acesso read-only aprovado e arquivar `migrate:status`/inventário, sem inferir pelo banco QA. | -| Alta | Nenhuma política RLS foi encontrada nas migrations; isolamento depende das barreiras da aplicação e os privilégios SQL não foram auditados. | Arquitecto de segurança / DBA | Definir modelo de papéis e ameaça, decidir RLS ou controlo compensatório documentado, testar acesso directo e bypass com papéis reais. | -| Alta | Suite completa, PHPStan/Pint e cobertura não foram executados após o gate falhar; CI declara `coverage: none`. | Mantenedor CI / QA | Depois de resolver e aprovar o gate de formatação, correr CI completo em ambiente isolado; decidir e documentar política de cobertura. | -| Média | OpenAPI fragmentado por domínio, versões 3.0.3/3.1.0 e sem validação global observada no workflow. | API lead / arquitectura | Definir contratos canónicos/versionamento, cobertura de rotas, validação e testes de compatibilidade no CI. | -| Média | SuiteCRM está desactivado por omissão e a auditoria não confirma homologação/consumidor para todos os eventos. | Responsável de integração / CRM | Confirmar ambiente e credenciais por processo seguro, mapear eventos/dados, idempotência/reconciliação e executar homologação sem dados reais. | -| Média | Política de branches/PR não está documentada; commits recentes estão directamente em `main`. | Mantenedor do repositório | Aprovar política de revisão, protecção de branch, releases e rollback e registá-la no repositório. | +| Severidade | Bloqueador e evidência | Responsável por função | Condição de desbloqueio | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Crítica — gate actual | `npm.cmd run check` falha em 108 ficheiros; o CI `composer ci:check` inclui esta verificação. | Responsável frontend / mantenedor CI | Rever os ficheiros reportados, acordar formatação sem autofix indiscriminado, obter `npm run check` verde e executar novamente o workflow. | +| Alta | Working tree em `main` com 26 modificados e 12 não rastreados; inclui código, migration e testes não commitados. | Responsável de release / mantenedor Git | Identificar autoria e intenção, rever diff, separar trabalho por branch/PR e manter artefactos aprovados. | +| Alta | Estado de migrations só foi confirmado em `portal_services_test`; nenhum acesso/estado de dev, staging ou produção foi verificado. | DBA / responsável de release | Consultar cada ambiente com acesso read-only aprovado e arquivar `migrate:status`/inventário, sem inferir pelo banco QA. | +| Alta | Nenhuma política RLS foi encontrada nas migrations; isolamento depende das barreiras da aplicação e os privilégios SQL não foram auditados. | Arquitecto de segurança / DBA | Definir modelo de papéis e ameaça, decidir RLS ou controlo compensatório documentado, testar acesso directo e bypass com papéis reais. | +| Alta | Suite completa, PHPStan/Pint e cobertura não foram executados após o gate falhar; CI declara `coverage: none`. | Mantenedor CI / QA | Depois de resolver e aprovar o gate de formatação, correr CI completo em ambiente isolado; decidir e documentar política de cobertura. | +| Média | OpenAPI fragmentado por domínio, versões 3.0.3/3.1.0 e sem validação global observada no workflow. | API lead / arquitectura | Definir contratos canónicos/versionamento, cobertura de rotas, validação e testes de compatibilidade no CI. | +| Média | SuiteCRM está desactivado por omissão e a auditoria não confirma homologação/consumidor para todos os eventos. | Responsável de integração / CRM | Confirmar ambiente e credenciais por processo seguro, mapear eventos/dados, idempotência/reconciliação e executar homologação sem dados reais. | +| Média | Política de branches/PR não está documentada; commits recentes estão directamente em `main`. | Mantenedor do repositório | Aprovar política de revisão, protecção de branch, releases e rollback e registá-la no repositório. | Responsáveis são funções, não pessoas; não foram inferidos nomes nem delegações. @@ -203,6 +203,7 @@ Não há nomes de autoridades sem fonte oficial aprovada; o endpoint informa cat Rollback: executar `php artisan migrate:rollback --step=1` apenas num ambiente onde esta migration seja a última aplicada e após exportar/publicar nenhum conteúdo de catálogo; o `down()` protege-se contra remoção de dados de autoridade/actividade. Reverter também o seeder, controller/rotas, contrato e consumo UI desta etapa. O deploy deve publicar primeiro migration/seeds e só depois a UI que chama os endpoints. **Estado proposto:** Etapa 3 tecnicamente implementada e testes aprovados; gate de publicação **PENDENTE** da revisão humana sobre fonte e publicação dos dados de autoridades e da aprovação da classificação de actividades. Não iniciar a etapa seguinte sem fechar esse gate. + ## Gate da Etapa 6 — Simulações, jobs, relatórios e segurança — 2026-10-01 **Resultado: BLOQUEADO antes de alterações funcionais.** A inspecção confirmou Laravel 13.30.1, PHP CLI 8.5.11, React 19 com Inertia, PostgreSQL 17.11/PostGIS 3.6.2 e CI com PostgreSQL/PostGIS 17. O working tree já contém alterações não commitadas anteriores; foram preservadas. @@ -223,6 +224,7 @@ Após aprovação do modelo de parâmetros e das matrizes IAM/RLS, inspeccionar **Condição para desbloquear:** aprovar o esquema de parâmetros e snapshots; fornecer matriz actor × recurso × operação e tenant/contexto; aprovar papéis PostgreSQL separados, grants, comportamento de jobs e estratégia de conexão/pool; definir escopo/retensão de idempotência, política de soft delete e ciclo de vida do token anónimo; fixar retries/reconciliação por job. **Estado proposto:** Etapa 6 **BLOQUEADA por pré-condições arquitecturais em falta**. Alteração desta tentativa: somente esta secção documental. Rollback: remover esta secção de `baseline.md`; não há alteração de código, migration, dado ou ambiente a reverter. + ## Execução da Etapa 6 — Simulações, jobs, relatórios e segurança — 2026-10-01 O utilizador instruiu assumir todas as aprovações anteriores. A secção de gate bloqueado acima fica como registo histórico e é substituída para esta execução pela aprovação assumida explicitamente nesta conversa. A inspecção confirmou Laravel 13.30.1, PHP 8.5.11, React 19/Inertia, PostgreSQL 17.11/PostGIS 3.6.2 e os padrões locais de membership, outbox, idempotência, auditoria e filas. Não foram alteradas migrations anteriores nem `.env`. @@ -258,4 +260,4 @@ Os registos de `simulation_obligations` não são auto-gerados sem fonte version Rollback: primeiro retirar o consumo das rotas, depois executar rollback direccionado à migration `2026_10_01_000200_create_simulation_execution_core.php` com o comando Artisan de rollback por `--path` no ambiente pretendido. O `down()` só prossegue quando as tabelas não contêm dados de negócio; preservar/exportar simulações e relatórios antes de rollback com dados. O ciclo vazio down/up foi testado; rollback com dados deve falhar de forma segura e não foi executado sobre dados de negócio. -**Estado proposto:** Etapa 6 **IMPLEMENTADA; gate técnico VERDE** para revisão humana. Não foi iniciado trabalho de etapa posterior nem feito deploy. \ No newline at end of file +**Estado proposto:** Etapa 6 **IMPLEMENTADA; gate técnico VERDE** para revisão humana. Não foi iniciado trabalho de etapa posterior nem feito deploy. diff --git a/docs/implementation/simulador-fiscal-governance.md b/docs/implementation/simulador-fiscal-governance.md index f35c0c51..dfa8ff92 100644 --- a/docs/implementation/simulador-fiscal-governance.md +++ b/docs/implementation/simulador-fiscal-governance.md @@ -9,11 +9,11 @@ Esta governação define o MVP fiscal sem substituir funcionalidades existentes do Portal do Investidor. O âmbito funcional do Simulador Fiscal fica limitado ao que está aprovado nesta especificação; não declara homologação, publicação de regras, integração operacional ou cobertura fiscal que não tenha evidência. -| Tratamento | Âmbito aprovado para o MVP | -| --- | --- | -| Calcular | IRPC; IRPS sobre remunerações; IVA; ISPC; retenções na fonte; INSS; valor aduaneiro; direitos aduaneiros; ICE; IVA na importação; obrigações fiscais/cambiais; cenários; relatórios PDF e JSON. | -| Identificar e orientar, sem cálculo automático | Imposto do selo; impostos autárquicos; mais-valias; preços de transferência; convenções completas de dupla tributação; benefícios complexos ZEE/ZFI; regimes sectoriais. | -| Fora desta release | Manifestações formais; oportunidades de negócio; conversões para projecto/oportunidade; CRM completo; conta avançada do investidor; assistente inteligente. | +| Tratamento | Âmbito aprovado para o MVP | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Calcular | IRPC; IRPS sobre remunerações; IVA; ISPC; retenções na fonte; INSS; valor aduaneiro; direitos aduaneiros; ICE; IVA na importação; obrigações fiscais/cambiais; cenários; relatórios PDF e JSON. | +| Identificar e orientar, sem cálculo automático | Imposto do selo; impostos autárquicos; mais-valias; preços de transferência; convenções completas de dupla tributação; benefícios complexos ZEE/ZFI; regimes sectoriais. | +| Fora desta release | Manifestações formais; oportunidades de negócio; conversões para projecto/oportunidade; CRM completo; conta avançada do investidor; assistente inteligente. | Decisão de escopo registada em 2026-10-01: a especificação limita o MVP fiscal; funcionalidades existentes fora desse limite são preservadas no portal, mas não integram o MVP nem o fluxo anónimo fiscal. @@ -30,17 +30,17 @@ Decisão de escopo registada em 2026-10-01: a especificação limita o MVP fisca ## Matriz de conformidade e rastreabilidade -| Requisito/fase | Evidência actual | Estado | Lacuna ou divergência | Ficheiros/áreas de referência | Próximo gate | -| --- | --- | --- | --- | --- | --- | -| Fase 0 — governação | Baseline, decisões e ADRs; este documento acrescenta escopo, RACI funcional, riscos, backlog e gates | CONFORME PARA INICIAR A FASE 1 | Responsáveis nominais e aprovações fiscal/jurídica/segurança/ops continuam necessários antes dos gates que delas dependem | `docs/implementation/baseline.md`, `docs/implementation/adr/`, este ficheiro | Gate da Fase 1 | -| Fase 1 — fundação e contratos executáveis | Migrations, mapa SQL/OpenAPI, contratos por domínio e teste de ciclo de schema | PARCIAL | Contratos fragmentados em 3.0.3/3.1.0; validação integral e reconciliação de todos os caminhos/colunas não demonstradas neste gate | `database/migrations/`, `docs/implementation/sql-openapi-map.csv`, `docs/investmoz/*/openapi.json` | Inventário aprovado, validação OpenAPI integral e testes seguros em DB descartável | -| Fase 2 — repositório legislativo | Existe rota de consulta de fontes legislativas e registos jurídicos noutros domínios | PARCIAL | Não há evidência suficiente de repositório fiscal versionado com disposições, vigência, hashes, relações, ingestão e pesquisa integral | `app/Http/Controllers/Api/V1/LegislationSourceController.php`, migrations, `routes/api.php` | Contrato e fontes oficiais aprovadas; verificar CRUD/importação e integridade | -| Fase 3 — regras e publicação | Há catálogos versionados e padrões de revisão noutros domínios | AUSENTE para regras fiscais | Não foi identificada evidência de rule sets fiscais, parâmetros com fonte oficial, elegibilidade, revisão de quatro olhos, testes dourados e publicação imutável | migrations, models/services fiscais (a inventariar na Fase 3) | Aprovação de fontes, regras, responsabilidade fiscal/jurídica e vigência | -| Fase 4 — API e formulário | `/Simulador`, catálogos, snapshots e execução aritmética existem | BLOQUEADO por divergência | O formulário contém identidade/contactos e finalidade de manifestação/oportunidade; a especificação limita o modo anónimo a dados económicos/fiscais/territoriais/operacionais e exclui esses fluxos. Decidir compatibilidade semântica antes de alterar a UI/API | `resources/js/lib/simulation-form.ts`, `resources/js/components/simulation-form.tsx`, `StoreSimulationFormDraftRequest.php`, `SimulationFormDrafts.php` | Decisão de produto/privacidade sobre fluxo anónimo e separação de funcionalidades fora da release | -| Fase 5 — motor fiscal | Motor actual executa aritmética genérica introduzida pelo utilizador | AUSENTE para o âmbito fiscal | Não há evidência de cálculos fiscais oficiais de IRPC, IVA, ISPC, IRPS, INSS ou retenções com regras/fontes aprovadas, Decimal, arredondamento fiscal e casos dourados | `app/Services/SimulationExecution.php`, `app/Jobs/RunSimulation.php`, `docs/investmoz/simulations/openapi.json` | Aprovação de regras fiscais e especificação matemática antes de implementação | -| Fase 6 — aduanas, câmbios e calendário | Campos/estrutura parcial de simulação e jobs genéricos | AUSENTE para os cálculos e calendário pedidos | Valor aduaneiro, direitos, ICE, IVA de importação, obrigações cambiais/fiscais e calendário não estão demonstrados como motor completo | app/migrations de simulação e integrações | Fontes e regras oficiais; contratos de moedas/origem e jobs/retries | -| Fase 7 — relatórios e homologação | Snapshot de relatório JSON e hashes existem | PARCIAL | PDF, reconciliação integral API/DB/PDF/JSON, homologação, acessibilidade manual, resiliência e DR não demonstrados | `SimulationReport`, controller/serviço de simulação, testes | Contrato PDF/JSON, casos dourados, acessibilidade e prova de recuperação | -| Fase 8 — release e estabilização | Workflow CI e compose de desenvolvimento existem | PARCIAL | Não há evidência de Go/No-Go fiscal, release imutável, backup/restauro, rollout/rollback ensaiados ou hypercare | `.github/workflows/tests.yml`, `deploy/docker/compose.yaml` | Aprovação de release, owners operacionais e evidência de rollout/rollback | +| Requisito/fase | Evidência actual | Estado | Lacuna ou divergência | Ficheiros/áreas de referência | Próximo gate | +| ----------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| Fase 0 — governação | Baseline, decisões e ADRs; este documento acrescenta escopo, RACI funcional, riscos, backlog e gates | CONFORME PARA INICIAR A FASE 1 | Responsáveis nominais e aprovações fiscal/jurídica/segurança/ops continuam necessários antes dos gates que delas dependem | `docs/implementation/baseline.md`, `docs/implementation/adr/`, este ficheiro | Gate da Fase 1 | +| Fase 1 — fundação e contratos executáveis | Migrations, mapa SQL/OpenAPI, contratos por domínio e teste de ciclo de schema | PARCIAL | Contratos fragmentados em 3.0.3/3.1.0; validação integral e reconciliação de todos os caminhos/colunas não demonstradas neste gate | `database/migrations/`, `docs/implementation/sql-openapi-map.csv`, `docs/investmoz/*/openapi.json` | Inventário aprovado, validação OpenAPI integral e testes seguros em DB descartável | +| Fase 2 — repositório legislativo | Existe rota de consulta de fontes legislativas e registos jurídicos noutros domínios | PARCIAL | Não há evidência suficiente de repositório fiscal versionado com disposições, vigência, hashes, relações, ingestão e pesquisa integral | `app/Http/Controllers/Api/V1/LegislationSourceController.php`, migrations, `routes/api.php` | Contrato e fontes oficiais aprovadas; verificar CRUD/importação e integridade | +| Fase 3 — regras e publicação | Há catálogos versionados e padrões de revisão noutros domínios | AUSENTE para regras fiscais | Não foi identificada evidência de rule sets fiscais, parâmetros com fonte oficial, elegibilidade, revisão de quatro olhos, testes dourados e publicação imutável | migrations, models/services fiscais (a inventariar na Fase 3) | Aprovação de fontes, regras, responsabilidade fiscal/jurídica e vigência | +| Fase 4 — API e formulário | `/Simulador`, catálogos, snapshots e execução aritmética existem | BLOQUEADO por divergência | O formulário contém identidade/contactos e finalidade de manifestação/oportunidade; a especificação limita o modo anónimo a dados económicos/fiscais/territoriais/operacionais e exclui esses fluxos. Decidir compatibilidade semântica antes de alterar a UI/API | `resources/js/lib/simulation-form.ts`, `resources/js/components/simulation-form.tsx`, `StoreSimulationFormDraftRequest.php`, `SimulationFormDrafts.php` | Decisão de produto/privacidade sobre fluxo anónimo e separação de funcionalidades fora da release | +| Fase 5 — motor fiscal | Motor actual executa aritmética genérica introduzida pelo utilizador | AUSENTE para o âmbito fiscal | Não há evidência de cálculos fiscais oficiais de IRPC, IVA, ISPC, IRPS, INSS ou retenções com regras/fontes aprovadas, Decimal, arredondamento fiscal e casos dourados | `app/Services/SimulationExecution.php`, `app/Jobs/RunSimulation.php`, `docs/investmoz/simulations/openapi.json` | Aprovação de regras fiscais e especificação matemática antes de implementação | +| Fase 6 — aduanas, câmbios e calendário | Campos/estrutura parcial de simulação e jobs genéricos | AUSENTE para os cálculos e calendário pedidos | Valor aduaneiro, direitos, ICE, IVA de importação, obrigações cambiais/fiscais e calendário não estão demonstrados como motor completo | app/migrations de simulação e integrações | Fontes e regras oficiais; contratos de moedas/origem e jobs/retries | +| Fase 7 — relatórios e homologação | Snapshot de relatório JSON e hashes existem | PARCIAL | PDF, reconciliação integral API/DB/PDF/JSON, homologação, acessibilidade manual, resiliência e DR não demonstrados | `SimulationReport`, controller/serviço de simulação, testes | Contrato PDF/JSON, casos dourados, acessibilidade e prova de recuperação | +| Fase 8 — release e estabilização | Workflow CI e compose de desenvolvimento existem | PARCIAL | Não há evidência de Go/No-Go fiscal, release imutável, backup/restauro, rollout/rollback ensaiados ou hypercare | `.github/workflows/tests.yml`, `deploy/docker/compose.yaml` | Aprovação de release, owners operacionais e evidência de rollout/rollback | ### Divergências que não serão resolvidas por inferência @@ -54,29 +54,29 @@ Decisão de escopo registada em 2026-10-01: a especificação limita o MVP fisca Não foram atribuídos nomes. A nomeação de responsáveis e a aprovação desta matriz são pendências do gate. -| Actividade | Responsável (R) | Accountable (A) | Consultados (C) | Informados (I) | -| --- | --- | --- | --- | --- | -| Aprovar fonte, regra, vigência e casos fiscais | Especialista fiscal | Dono de produto fiscal | Jurídico, dados, engenharia | QA, operações | -| Publicar regra versionada | Engenharia fiscal | Dono de produto fiscal | Fiscal, jurídico, segurança | QA, operações | -| Aprovar privacidade e fluxo anónimo | Privacidade/protecção de dados | Dono de produto | Jurídico, segurança, fiscal | Engenharia, QA | -| Aprovar IAM/RLS/grants | DBA/segurança | Arquitectura de segurança | Engenharia, operações | Dono de produto, QA | -| Homologar cálculos e relatórios | QA fiscal | Dono de produto fiscal | Fiscal, jurídico, engenharia | Operações | -| Autorizar release, rollback e DR | Operações/release | Dono de serviço | DBA, segurança, QA, produto | Stakeholders | +| Actividade | Responsável (R) | Accountable (A) | Consultados (C) | Informados (I) | +| ---------------------------------------------- | ------------------------------ | ------------------------- | ---------------------------- | ------------------- | +| Aprovar fonte, regra, vigência e casos fiscais | Especialista fiscal | Dono de produto fiscal | Jurídico, dados, engenharia | QA, operações | +| Publicar regra versionada | Engenharia fiscal | Dono de produto fiscal | Fiscal, jurídico, segurança | QA, operações | +| Aprovar privacidade e fluxo anónimo | Privacidade/protecção de dados | Dono de produto | Jurídico, segurança, fiscal | Engenharia, QA | +| Aprovar IAM/RLS/grants | DBA/segurança | Arquitectura de segurança | Engenharia, operações | Dono de produto, QA | +| Homologar cálculos e relatórios | QA fiscal | Dono de produto fiscal | Fiscal, jurídico, engenharia | Operações | +| Autorizar release, rollback e DR | Operações/release | Dono de serviço | DBA, segurança, QA, produto | Stakeholders | RACI é uma proposta de funções e não atribui autoridade a pessoas. Gate bloqueado até os responsáveis formais serem indicados e aceitarem. ## Backlog e gates de aceitação -| Prioridade | Item | Dependência | Critério de conclusão | -| --- | --- | --- | --- | -| P0 | Resolver divergência do modo anónimo, PII e funcionalidades fora da release | Decisão de produto e privacidade | Formulário e contrato aprovados sem recolha anónima vedada; decisão registada | -| P0 | Aprovar fontes, regras, vigência e arredondamento de cada cálculo | Autoridade fiscal e jurídico | Fonte oficial/hash, regra versionada, casos dourados e dupla revisão registados | -| P0 | Aprovar matriz IAM/RLS e papéis SQL | Segurança/DBA | Testes actor×recurso×operação com papel runtime sem bypass e contexto limpo | -| P1 | Reconciliar OpenAPI integral, migrations e esquema | API/DBA | Validação semântica de contratos e cobertura de operações/campos no CI | -| P1 | Implementar cálculos e obrigações do escopo | Regras aprovadas | Testes críticos 100%, totais aplicáveis ≥95%, Decimal e resultados explicáveis | -| P1 | Aduanas, câmbios, calendário e jobs | Fontes/regras e contrato operacional | Casos dourados, idempotência/retry, reconciliação e dead-letter demonstrados | -| P1 | PDF/JSON e acessibilidade/homologação | Schema de relatório e casos aprovados | API/DB/PDF/JSON reconciliados; teclado/leitor de ecrã/zoom e homologação registados | -| P1 | Preparar release, backup/restauro e rollback | Operações/DBA | Release imutável, backup restaurado em prova, smoke tests e rollback ensaiados | +| Prioridade | Item | Dependência | Critério de conclusão | +| ---------- | --------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------- | +| P0 | Resolver divergência do modo anónimo, PII e funcionalidades fora da release | Decisão de produto e privacidade | Formulário e contrato aprovados sem recolha anónima vedada; decisão registada | +| P0 | Aprovar fontes, regras, vigência e arredondamento de cada cálculo | Autoridade fiscal e jurídico | Fonte oficial/hash, regra versionada, casos dourados e dupla revisão registados | +| P0 | Aprovar matriz IAM/RLS e papéis SQL | Segurança/DBA | Testes actor×recurso×operação com papel runtime sem bypass e contexto limpo | +| P1 | Reconciliar OpenAPI integral, migrations e esquema | API/DBA | Validação semântica de contratos e cobertura de operações/campos no CI | +| P1 | Implementar cálculos e obrigações do escopo | Regras aprovadas | Testes críticos 100%, totais aplicáveis ≥95%, Decimal e resultados explicáveis | +| P1 | Aduanas, câmbios, calendário e jobs | Fontes/regras e contrato operacional | Casos dourados, idempotência/retry, reconciliação e dead-letter demonstrados | +| P1 | PDF/JSON e acessibilidade/homologação | Schema de relatório e casos aprovados | API/DB/PDF/JSON reconciliados; teclado/leitor de ecrã/zoom e homologação registados | +| P1 | Preparar release, backup/restauro e rollback | Operações/DBA | Release imutável, backup restaurado em prova, smoke tests e rollback ensaiados | ## Métrica, aprovação e bloqueio diff --git a/docs/investmoz/ai/api.md b/docs/investmoz/ai/api.md index 8780be98..440c9ab5 100644 --- a/docs/investmoz/ai/api.md +++ b/docs/investmoz/ai/api.md @@ -2,16 +2,16 @@ Base `/api/v1/ai`. Autenticação por sessão Laravel, email verificado, `manage-portal`, feature flag e CSRF do middleware web. Não é API OAuth/OIDC para terceiros. Enviar `Accept: application/json`; o React usa as rotas equivalentes `/management/ai` via Inertia. -| Método / recurso | Entrada | Resultado | -| --- | --- | --- | -| POST `/runs` | capability, purpose, language; query para assistant/search; source_id para summary; interest_id para classify; sector_id/province/maturity_level opcionais para recommend; source_id/nature opcionais para search | 201 com UUID; finalidade verificada também pelo gateway | -| GET `/runs/{uuid}` | UUID | Metadados e resultado, ou resultado nulo quando fonte/consentimento revogados ou capacidade suspensa | -| POST `/runs/{uuid}/feedback` | rating: helpful/unhelpful/unsafe; review_requested boolean | Feedback idempotente por utilizador/resultado; unsafe suspende a capacidade | -| POST `/runs/{uuid}/review` | status: accepted/rejected; version_no | Revisão independente, conflito 409 em versão antiga; nenhum envio externo | -| POST `/documents` | Multipart file texto simples, authorised=true | 201 document_id canónico; não torna a fonte pesquisável | -| POST `/sources` | document_id, title, language pt/en, nature, source_url HTTPS, source_owner, evidence_reference, source_date, valid_until, authorised=true | Fonte pendente e fragmentos indexados, ainda inelegíveis | -| POST `/sources/{uuid}/review` | status: approved/revoked; expected_status | Confirmação por outra pessoa para aprovar; conflito se estado mudou | -| POST `/controls/{capability}` | suspended boolean | Suspensão imediata no armazenamento partilhado; fora do limite de consultas, mantendo autorização/CSRF | +| Método / recurso | Entrada | Resultado | +| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| POST `/runs` | capability, purpose, language; query para assistant/search; source_id para summary; interest_id para classify; sector_id/province/maturity_level opcionais para recommend; source_id/nature opcionais para search | 201 com UUID; finalidade verificada também pelo gateway | +| GET `/runs/{uuid}` | UUID | Metadados e resultado, ou resultado nulo quando fonte/consentimento revogados ou capacidade suspensa | +| POST `/runs/{uuid}/feedback` | rating: helpful/unhelpful/unsafe; review_requested boolean | Feedback idempotente por utilizador/resultado; unsafe suspende a capacidade | +| POST `/runs/{uuid}/review` | status: accepted/rejected; version_no | Revisão independente, conflito 409 em versão antiga; nenhum envio externo | +| POST `/documents` | Multipart file texto simples, authorised=true | 201 document_id canónico; não torna a fonte pesquisável | +| POST `/sources` | document_id, title, language pt/en, nature, source_url HTTPS, source_owner, evidence_reference, source_date, valid_until, authorised=true | Fonte pendente e fragmentos indexados, ainda inelegíveis | +| POST `/sources/{uuid}/review` | status: approved/revoked; expected_status | Confirmação por outra pessoa para aprovar; conflito se estado mudou | +| POST `/controls/{capability}` | suspended boolean | Suspensão imediata no armazenamento partilhado; fora do limite de consultas, mantendo autorização/CSRF | Capacidades: assistant, search, recommend, classify, quality, sla, summary. Finalidades: investment_information para pesquisa/recomendação/excertos; interest_triage para triagem; data_quality e execution_monitoring para monitores. diff --git a/docs/investmoz/ai/architecture.md b/docs/investmoz/ai/architecture.md index 7806f8c6..03205326 100644 --- a/docs/investmoz/ai/architecture.md +++ b/docs/investmoz/ai/architecture.md @@ -30,15 +30,15 @@ O React apenas comunica com Laravel. A primeira interface é interna, para gesto ## 3. Linha de base dos sete casos -| Caso | Execução inicial | Limite explícito | -| --- | --- | --- | -| UC-IA-001 | Recuperar excertos autorizados com fonte, data, natureza e recusa sem resultados | Sem resposta generativa ou aconselhamento jurídico/financeiro | -| UC-IA-002 | Full-text search PT/EN, filtros de fontes e acesso antes da recuperação | Híbrido/embeddings/reranker exigem infraestrutura e benchmark | +| Caso | Execução inicial | Limite explícito | +| --------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| UC-IA-001 | Recuperar excertos autorizados com fonte, data, natureza e recusa sem resultados | Sem resposta generativa ou aconselhamento jurídico/financeiro | +| UC-IA-002 | Full-text search PT/EN, filtros de fontes e acesso antes da recuperação | Híbrido/embeddings/reranker exigem infraestrutura e benchmark | | UC-IA-003 | Correspondência estruturada com oportunidades publicadas, factores, lacunas de informação e regras versionadas | Sem previsão de retorno, pontuação financeira ou inferência sensível | -| UC-IA-004 | Regras sobre campos declarados da manifestação, sem usar nome/email/telefone | Não altera lead, prioridade ou responsável oficial | -| UC-IA-005 | Alertas sobre campos, fontes expiradas e responsáveis | Nenhuma correcção automática de dados | -| UC-IA-006 | Prazos vencidos de tarefas CRM abertas | Sem caminho crítico ou impacto orçamental inventado | -| UC-IA-007 | Excertos de documentos autorizados | Não constitui resumo generativo completo nem acta oficial | +| UC-IA-004 | Regras sobre campos declarados da manifestação, sem usar nome/email/telefone | Não altera lead, prioridade ou responsável oficial | +| UC-IA-005 | Alertas sobre campos, fontes expiradas e responsáveis | Nenhuma correcção automática de dados | +| UC-IA-006 | Prazos vencidos de tarefas CRM abertas | Sem caminho crítico ou impacto orçamental inventado | +| UC-IA-007 | Excertos de documentos autorizados | Não constitui resumo generativo completo nem acta oficial | ## 4. Segurança e governação diff --git a/docs/investmoz/ai/benchmark-results.json b/docs/investmoz/ai/benchmark-results.json index 644c93f3..b6de4d01 100644 --- a/docs/investmoz/ai/benchmark-results.json +++ b/docs/investmoz/ai/benchmark-results.json @@ -1,21 +1,82 @@ { - "executed_at": "2026-09-26T15:51:08+00:00", - "backend": "PostgreSQL FTS", - "model": null, - "synthetic": true, - "passed": 8, - "cases": 8, - "repetitions_per_case": 5, - "measurements": [ - {"language":"pt","query":"formação técnica","expected_match":true,"passed":true,"p50_ms":1.604,"max_ms":8.87}, - {"language":"pt","query":"cadeia logística","expected_match":true,"passed":true,"p50_ms":1.228,"max_ms":1.551}, - {"language":"pt","query":"energia solar","expected_match":true,"passed":true,"p50_ms":1.207,"max_ms":1.724}, - {"language":"pt","query":"taxa oficial","expected_match":false,"passed":true,"p50_ms":1.091,"max_ms":1.457}, - {"language":"en","query":"vocational training","expected_match":true,"passed":true,"p50_ms":1.237,"max_ms":1.518}, - {"language":"en","query":"logistics corridor","expected_match":true,"passed":true,"p50_ms":1.233,"max_ms":1.3}, - {"language":"en","query":"solar energy","expected_match":true,"passed":true,"p50_ms":1.23,"max_ms":1.426}, - {"language":"en","query":"official tax","expected_match":false,"passed":true,"p50_ms":1.167,"max_ms":1.337} - ], - "generative_benchmark": "not_executed_no_runtime_or_model", - "limitations": ["small_single_source_cases", "not_a_representative_benchmark", "not_human_validated_PT_MZ", "no_concurrency_or_load_test"] + "executed_at": "2026-09-26T15:51:08+00:00", + "backend": "PostgreSQL FTS", + "model": null, + "synthetic": true, + "passed": 8, + "cases": 8, + "repetitions_per_case": 5, + "measurements": [ + { + "language": "pt", + "query": "formação técnica", + "expected_match": true, + "passed": true, + "p50_ms": 1.604, + "max_ms": 8.87 + }, + { + "language": "pt", + "query": "cadeia logística", + "expected_match": true, + "passed": true, + "p50_ms": 1.228, + "max_ms": 1.551 + }, + { + "language": "pt", + "query": "energia solar", + "expected_match": true, + "passed": true, + "p50_ms": 1.207, + "max_ms": 1.724 + }, + { + "language": "pt", + "query": "taxa oficial", + "expected_match": false, + "passed": true, + "p50_ms": 1.091, + "max_ms": 1.457 + }, + { + "language": "en", + "query": "vocational training", + "expected_match": true, + "passed": true, + "p50_ms": 1.237, + "max_ms": 1.518 + }, + { + "language": "en", + "query": "logistics corridor", + "expected_match": true, + "passed": true, + "p50_ms": 1.233, + "max_ms": 1.3 + }, + { + "language": "en", + "query": "solar energy", + "expected_match": true, + "passed": true, + "p50_ms": 1.23, + "max_ms": 1.426 + }, + { + "language": "en", + "query": "official tax", + "expected_match": false, + "passed": true, + "p50_ms": 1.167, + "max_ms": 1.337 + } + ], + "generative_benchmark": "not_executed_no_runtime_or_model", + "limitations": [ + "small_single_source_cases", + "not_a_representative_benchmark", + "not_human_validated_PT_MZ", + "no_concurrency_or_load_test" + ] } diff --git a/docs/investmoz/ai/coverage.md b/docs/investmoz/ai/coverage.md index 7c77a663..e8a4473c 100644 --- a/docs/investmoz/ai/coverage.md +++ b/docs/investmoz/ai/coverage.md @@ -2,29 +2,29 @@ ## Casos do pedido -| Caso | Implementado nesta etapa | Pendente | -| --- | --- | --- | -| UC-IA-001 | Excertos citados de fontes elegíveis, data/natureza, recusa sem evidência e pedido de revisão | Respostas generativas fundamentadas, corpus oficial, assistência pública | -| UC-IA-002 | FTS PT/EN, idioma, fonte, natureza e acesso antes da recuperação; fusão RRF unitariamente testada | Embeddings, índice pgvector, reranker e filtros territoriais/multidimensionais completos; RRF ainda não ligado a vectores reais | -| UC-IA-003 | Filtros eliminatórios de publicação/sector/província/maturidade, factores e dados em falta; apenas oportunidades canónicas publicadas | Perfil completo, regras institucionais aprovadas, similitude semântica, clusters/corredores/instrumentos/parceiros; pontuação actual conta filtros, não é ponderação financeira | -| UC-IA-004 | Triagem por campos declarados, consentimento específico, sem dados de contacto ou texto livre; não altera pedido/lead | Modelo supervisionado, responsável sugerido, idioma/cluster/corredor/horizonte e regras de prioridade aprovadas | -| UC-IA-005 | Fontes expiradas, documentos com checksum repetido, ausência de responsável/próxima acção, geometrias inválidas; alertas em resultados auditados | Conflitos semânticos entre fontes, sincronização SuiteCRM e cobertura de todos os domínios | -| UC-IA-006 | Tarefas CRM abertas e vencidas, prazo, referência e acção de revisão sugerida | Marcos/dependências, caminho crítico, impacto e orçamento autorizado | -| UC-IA-007 | Pré-visualização extractiva dos primeiros três fragmentos de fonte elegível | Resumo generativo completo, reuniões/actas/históricos e extracção de compromissos confirmados | +| Caso | Implementado nesta etapa | Pendente | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UC-IA-001 | Excertos citados de fontes elegíveis, data/natureza, recusa sem evidência e pedido de revisão | Respostas generativas fundamentadas, corpus oficial, assistência pública | +| UC-IA-002 | FTS PT/EN, idioma, fonte, natureza e acesso antes da recuperação; fusão RRF unitariamente testada | Embeddings, índice pgvector, reranker e filtros territoriais/multidimensionais completos; RRF ainda não ligado a vectores reais | +| UC-IA-003 | Filtros eliminatórios de publicação/sector/província/maturidade, factores e dados em falta; apenas oportunidades canónicas publicadas | Perfil completo, regras institucionais aprovadas, similitude semântica, clusters/corredores/instrumentos/parceiros; pontuação actual conta filtros, não é ponderação financeira | +| UC-IA-004 | Triagem por campos declarados, consentimento específico, sem dados de contacto ou texto livre; não altera pedido/lead | Modelo supervisionado, responsável sugerido, idioma/cluster/corredor/horizonte e regras de prioridade aprovadas | +| UC-IA-005 | Fontes expiradas, documentos com checksum repetido, ausência de responsável/próxima acção, geometrias inválidas; alertas em resultados auditados | Conflitos semânticos entre fontes, sincronização SuiteCRM e cobertura de todos os domínios | +| UC-IA-006 | Tarefas CRM abertas e vencidas, prazo, referência e acção de revisão sugerida | Marcos/dependências, caminho crítico, impacto e orçamento autorizado | +| UC-IA-007 | Pré-visualização extractiva dos primeiros três fragmentos de fonte elegível | Resumo generativo completo, reuniões/actas/históricos e extracção de compromissos confirmados | Nenhum caso é declarado integralmente concluído. A aceitação da revisão apenas grava um parecer interno. Não altera estados oficiais, cria compromissos ou transmite dados ao SuiteCRM. ## Pipeline RAG de 17 passos -| Passos | Situação | -| --- | --- | -| 1–2: recepção e segurança | Cadastro canónico, upload apenas texto UTF-8, limite de tamanho, confirmação de autorização, rejeição de fontes confidenciais/restritas; formatos complexos/OCR/antivírus pendentes | +| Passos | Situação | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1–2: recepção e segurança | Cadastro canónico, upload apenas texto UTF-8, limite de tamanho, confirmação de autorização, rejeição de fontes confidenciais/restritas; formatos complexos/OCR/antivírus pendentes | | 3–6: extracção, versão, acesso e fragmentação | Texto simples, versão/checksum do documento, metadados de fonte, revisão independente, fragmentos com ID/checksum/posição; divisão por caracteres não substitui tokenização do modelo | -| 7–8: embeddings e pgvector | Não executados. Extensão/runtime ausentes. Esquema proposto separado, sem tocar na base partilhada | -| 9–10: índice lexical e ACL | tsvector gerado por idioma, GIN; status, validade, classificação, versão e checksum filtrados antes da pesquisa | -| 11–12: híbrido e reranking | Algoritmo RRF testado isoladamente; corpus vectorial/modelos não provisionados, logo produção continua lexical | -| 13–15: contexto, geração e suporte | Excertos devolvidos como citações; sem geração nem afirmações novas. Verificação semântica de afirmações depende de modelo e avaliação | -| 16–17: fontes e rastreabilidade | Fonte/data/natureza/validade, modo, regras, versão de instrução e latência; resultado encriptado; sem falsa confiança probabilística | +| 7–8: embeddings e pgvector | Não executados. Extensão/runtime ausentes. Esquema proposto separado, sem tocar na base partilhada | +| 9–10: índice lexical e ACL | tsvector gerado por idioma, GIN; status, validade, classificação, versão e checksum filtrados antes da pesquisa | +| 11–12: híbrido e reranking | Algoritmo RRF testado isoladamente; corpus vectorial/modelos não provisionados, logo produção continua lexical | +| 13–15: contexto, geração e suporte | Excertos devolvidos como citações; sem geração nem afirmações novas. Verificação semântica de afirmações depende de modelo e avaliação | +| 16–17: fontes e rastreabilidade | Fonte/data/natureza/validade, modo, regras, versão de instrução e latência; resultado encriptado; sem falsa confiança probabilística | ## Metadados diff --git a/docs/investmoz/ai/models.md b/docs/investmoz/ai/models.md index d5f75bb6..e7732631 100644 --- a/docs/investmoz/ai/models.md +++ b/docs/investmoz/ai/models.md @@ -2,15 +2,15 @@ Consulta de fontes primárias em 26/09/2026. São candidatos identificados, não alegações de versões mais recentes nem selecção final. Licença do modelo não elimina obrigações de proveniência dos dados nem valida juridicamente o uso em Moçambique. -| Candidato fixado para avaliação | Licença declarada | Recursos e decisão | -| --- | --- | --- | -| mistralai/Mistral-Small-3.2-24B-Instruct-2506 | Apache-2.0 | A ficha indica cerca de 55 GB GPU em BF16/FP16. Incompatível com a GPU virtual disponível. Quantização precisa de benchmark próprio. | -| Qwen/Qwen3-4B-Instruct-2507 | Apache-2.0 | Estimativa aritmética de pesos: cerca de 8 GB a 16 bits ou 2 GB a 4 bits, antes de KV cache, runtime e metadados. Candidato menor para benchmark, não aprovado. | -| BAAI/bge-m3 | MIT | Embeddings de 1024 dimensões, contexto máximo indicado de 8192 tokens; memória e latência locais não medidas. | -| BAAI/bge-reranker-v2-m3 | Apache-2.0 | Cross-encoder multilingue; custo cresce com pares pergunta/fragmento. Medir antes de habilitar. | -| pgvector 0.8.6 | PostgreSQL License | Código open source; extensão ausente no PostgreSQL local. Usar a mesma base, não uma base paralela. | -| vLLM | Verificar licença/release do artefacto de implantação | Ambiente de inferência controlado por provisionar; não instalado nem validado nesta máquina. | -| Ollama | Apenas protótipo local, conforme pedido | Não instalado. Não tratar o Windows virtual como equivalente ao hardware macOS anfitrião. | +| Candidato fixado para avaliação | Licença declarada | Recursos e decisão | +| --------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| mistralai/Mistral-Small-3.2-24B-Instruct-2506 | Apache-2.0 | A ficha indica cerca de 55 GB GPU em BF16/FP16. Incompatível com a GPU virtual disponível. Quantização precisa de benchmark próprio. | +| Qwen/Qwen3-4B-Instruct-2507 | Apache-2.0 | Estimativa aritmética de pesos: cerca de 8 GB a 16 bits ou 2 GB a 4 bits, antes de KV cache, runtime e metadados. Candidato menor para benchmark, não aprovado. | +| BAAI/bge-m3 | MIT | Embeddings de 1024 dimensões, contexto máximo indicado de 8192 tokens; memória e latência locais não medidas. | +| BAAI/bge-reranker-v2-m3 | Apache-2.0 | Cross-encoder multilingue; custo cresce com pares pergunta/fragmento. Medir antes de habilitar. | +| pgvector 0.8.6 | PostgreSQL License | Código open source; extensão ausente no PostgreSQL local. Usar a mesma base, não uma base paralela. | +| vLLM | Verificar licença/release do artefacto de implantação | Ambiente de inferência controlado por provisionar; não instalado nem validado nesta máquina. | +| Ollama | Apenas protótipo local, conforme pedido | Não instalado. Não tratar o Windows virtual como equivalente ao hardware macOS anfitrião. | Estimativa de armazenamento vectorial bruto BGE-M3: 1024 × 4 bytes = 4096 bytes por fragmento; um milhão de fragmentos representa aproximadamente 4,1 GB decimais, sem índices, texto, WAL, backups e overhead. Planear esses adicionais por medição. diff --git a/docs/investmoz/architecture-data-security-qa-product-review.md b/docs/investmoz/architecture-data-security-qa-product-review.md index 50c613bd..7603ccea 100644 --- a/docs/investmoz/architecture-data-security-qa-product-review.md +++ b/docs/investmoz/architecture-data-security-qa-product-review.md @@ -66,13 +66,13 @@ QA de aceitação: pesquisa por um projecto documental publicado deve devolvê-l ## Avaliação transversal -| Área | Evidência e conclusão | -| --- | --- | -| Arquitectura | Laravel centraliza autorização e persistência; serviços de IA separados de controllers. R1 e R4 mostram necessidade de versões explícitas entre edição, cadastro e publicação. | -| Dados | Importação transaccional/idempotente, códigos documentais e relações preservados. Valores indicativos continuam em texto. R3 impede reconstituição completa do histórico editorial. | -| Segurança | Rotas de gestão exigem autenticação, email verificado e `manage-portal`; resultados IA encriptados e fontes documentais revalidadas. R2 deixa uma lacuna equivalente nas dependências estruturadas. | -| QA | Execução anterior registada: 119 testes / 1.107 verificações, PHPStan, Pint, TypeScript e build aprovados. Não repetidos nesta revisão. Os cenários de concorrência e reclassificação acima precisam de cobertura; testes verdes não os demonstram. | -| Produto | Consulta territorial, relações, painel e manifestação de interesse estão disponíveis. A assistência continua lexical/determinística e a integração externa SuiteCRM não foi executada. R5 limita a descoberta da carteira via IA. | +| Área | Evidência e conclusão | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Arquitectura | Laravel centraliza autorização e persistência; serviços de IA separados de controllers. R1 e R4 mostram necessidade de versões explícitas entre edição, cadastro e publicação. | +| Dados | Importação transaccional/idempotente, códigos documentais e relações preservados. Valores indicativos continuam em texto. R3 impede reconstituição completa do histórico editorial. | +| Segurança | Rotas de gestão exigem autenticação, email verificado e `manage-portal`; resultados IA encriptados e fontes documentais revalidadas. R2 deixa uma lacuna equivalente nas dependências estruturadas. | +| QA | Execução anterior registada: 119 testes / 1.107 verificações, PHPStan, Pint, TypeScript e build aprovados. Não repetidos nesta revisão. Os cenários de concorrência e reclassificação acima precisam de cobertura; testes verdes não os demonstram. | +| Produto | Consulta territorial, relações, painel e manifestação de interesse estão disponíveis. A assistência continua lexical/determinística e a integração externa SuiteCRM não foi executada. R5 limita a descoberta da carteira via IA. | ## Ordem de tratamento diff --git a/docs/investmoz/business-marketplace-schema-integration.md b/docs/investmoz/business-marketplace-schema-integration.md index 8cfd6d16..582dbddc 100644 --- a/docs/investmoz/business-marketplace-schema-integration.md +++ b/docs/investmoz/business-marketplace-schema-integration.md @@ -4,20 +4,20 @@ O SQL recebido foi convertido numa migração incremental para PostgreSQL/PostGI ## Correspondências -| SQL recebido | Modelo actual | -| --- | --- | -| `business.parties` | Contas autenticadas em `users`; proponentes institucionais em `organizations`; perfis de investimento em `investors` e `investor_accounts`. Não é criada uma segunda tabela de identidade. | -| `business.opportunities` | `existing_businesses`: `tracking_code` usa `code`; proponente/criador usa `owner_user_id`; proprietário institucional usa `organization_id`; sector usa `sector_id`; versão usa `version_no`. | -| `business.opportunity_locations` | Nova tabela de relações `existing_business_locations`, ligada a `geo_locations` por UUID e com validação da hierarquia e geometria PostGIS. | -| `business.assets` | Campos tipados adicionais em `existing_business_objects`; o `payload` cifrado actual continua a guardar pormenores privados. | -| `business.rights` | Nova tabela `existing_business_rights`, ligada ao negócio e, quando aplicável, ao objecto. O detalhe reservado fica em `confidential_payload`, que os serviços devem cifrar. | -| `business.documents` | `documents` actual (`file_name`, `media_type`, `storage_key`, `sha256`, `status` e `data_classification`), com metadados adicionais; nova tabela de ligação ao negócio ou ao pedido de interesse. | -| `business.investor_profiles` | Campos de perfil em `investors`, cuja ligação à conta autenticada já existe em `investor_accounts`. | -| `business.interests` | `interest_requests`; acrescenta alvo `existing_business_id`, perfil do investidor, modalidade, montantes e atribuição. `opportunity_id` continua a referir-se ao catálogo público de investimentos, e uma submissão só pode indicar um desses dois alvos. | -| `business.consents` | `consents` actual, com alvo opcional e versão da política; os estados existentes `granted`/`withdrawn` mantêm-se como fonte canónica. | -| `business.workflow_transitions` | Histórico actual de revisões/reviews de negócios mais `business_workflow_transitions` para transições de negócios e pedidos de interesse. | -| `business.tasks` / `business.outbox_messages` | `tasks` e `outbox_events` existentes recebem campos adicionais; não são criadas filas paralelas. | -| `business.crm_links` | Nova tabela `external_record_links` para mapear identificadores remotos sem duplicar os registos de CRM. | +| SQL recebido | Modelo actual | +| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `business.parties` | Contas autenticadas em `users`; proponentes institucionais em `organizations`; perfis de investimento em `investors` e `investor_accounts`. Não é criada uma segunda tabela de identidade. | +| `business.opportunities` | `existing_businesses`: `tracking_code` usa `code`; proponente/criador usa `owner_user_id`; proprietário institucional usa `organization_id`; sector usa `sector_id`; versão usa `version_no`. | +| `business.opportunity_locations` | Nova tabela de relações `existing_business_locations`, ligada a `geo_locations` por UUID e com validação da hierarquia e geometria PostGIS. | +| `business.assets` | Campos tipados adicionais em `existing_business_objects`; o `payload` cifrado actual continua a guardar pormenores privados. | +| `business.rights` | Nova tabela `existing_business_rights`, ligada ao negócio e, quando aplicável, ao objecto. O detalhe reservado fica em `confidential_payload`, que os serviços devem cifrar. | +| `business.documents` | `documents` actual (`file_name`, `media_type`, `storage_key`, `sha256`, `status` e `data_classification`), com metadados adicionais; nova tabela de ligação ao negócio ou ao pedido de interesse. | +| `business.investor_profiles` | Campos de perfil em `investors`, cuja ligação à conta autenticada já existe em `investor_accounts`. | +| `business.interests` | `interest_requests`; acrescenta alvo `existing_business_id`, perfil do investidor, modalidade, montantes e atribuição. `opportunity_id` continua a referir-se ao catálogo público de investimentos, e uma submissão só pode indicar um desses dois alvos. | +| `business.consents` | `consents` actual, com alvo opcional e versão da política; os estados existentes `granted`/`withdrawn` mantêm-se como fonte canónica. | +| `business.workflow_transitions` | Histórico actual de revisões/reviews de negócios mais `business_workflow_transitions` para transições de negócios e pedidos de interesse. | +| `business.tasks` / `business.outbox_messages` | `tasks` e `outbox_events` existentes recebem campos adicionais; não são criadas filas paralelas. | +| `business.crm_links` | Nova tabela `external_record_links` para mapear identificadores remotos sem duplicar os registos de CRM. | Campos confidenciais já protegidos no `payload` cifrado não são copiados para colunas em texto aberto. O campo `public_title` fica separado para publicação futura; esta migração não publica negócios nem muda a máquina de estados da aplicação. @@ -37,12 +37,12 @@ Esta entrega altera o esquema e a serialização do modelo. Os endpoints/formul A implementação da proposta reutiliza os serviços e modelos que já existem, sem introduzir `Opportunity`, `Interest`, `Party` ou `OutboxMessage` paralelos: -| Responsabilidade proposta | Implementação no portal | -| --- | --- | -| Criar e actualizar rascunhos | `ExistingBusinessDrafts`, usado pela API autenticada `api/v1/existing-business/listings`. | -| Submeter e rever anúncios | `ExistingBusinessIntake`, com autorização via `ExistingBusinessPolicy`, controlo de versão e histórico cifrado. | -| Pedidos de interesse idempotentes | `InterestRequestController` e `InterestSubmission`; eventos são gravados na outbox existente. | -| Integração CRM | Cliente seguro `SuiteCrmClient`; a outbox existente usa `aggregate_type`, `aggregate_id`, `event_type`, `payload` e estados `pending/published/failed`. | -| Estados tipados | Enums `OpportunityStatus` e `InterestStatus` em `app/Domain`, mantendo os valores de estado já persistidos. | +| Responsabilidade proposta | Implementação no portal | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Criar e actualizar rascunhos | `ExistingBusinessDrafts`, usado pela API autenticada `api/v1/existing-business/listings`. | +| Submeter e rever anúncios | `ExistingBusinessIntake`, com autorização via `ExistingBusinessPolicy`, controlo de versão e histórico cifrado. | +| Pedidos de interesse idempotentes | `InterestRequestController` e `InterestSubmission`; eventos são gravados na outbox existente. | +| Integração CRM | Cliente seguro `SuiteCrmClient`; a outbox existente usa `aggregate_type`, `aggregate_id`, `event_type`, `payload` e estados `pending/published/failed`. | +| Estados tipados | Enums `OpportunityStatus` e `InterestStatus` em `app/Domain`, mantendo os valores de estado já persistidos. | Os estados adicionais do enum `OpportunityStatus` alimentam agora as etapas de triagem, revisão, duas aprovações e publicação de `ExistingBusinessIntake`. A publicação usa uma projecção pública separada e allowlist de campos. As manifestações de interesse têm uma máquina de estados própria em `InterestWorkflow`; transições, notas cifradas e prazos são guardados no histórico comum. Isto ainda não constitui uma integração CRM em background, SLA automatizado, upload/scanner de documentos ou autorização institucional para publicar dados reais. diff --git a/docs/investmoz/catalogue-review-2026-10-04.md b/docs/investmoz/catalogue-review-2026-10-04.md index 57718ddb..0240a32c 100644 --- a/docs/investmoz/catalogue-review-2026-10-04.md +++ b/docs/investmoz/catalogue-review-2026-10-04.md @@ -14,50 +14,50 @@ Data: 04/10/2026. Revisão feita por leitura da base activa e do código de impo Todas as linhas abaixo têm `source_system=upgrade-v4`, `validation_status=pending`, `association_type=potential`, `source_reference=NULL`, `evidence_reference=NULL`, `valid_from=NULL`, `valid_until=NULL`, `editor_id=NULL` e `validator_id=NULL`. A correspondência está registada na matriz de importação da secção 4.3.4 do documento de programação indicado pelo seeder; não há citação individual de página/documento oficial ou evidência que permita elevar o tipo para `official`. -| Corredor | Cluster | Resultado da revisão | -|---|---|---| -| COR-02 Corredor da Beira e Centro | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-02 Corredor da Beira e Centro | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-02 Corredor da Beira e Centro | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-02 Corredor da Beira e Centro | CLU-05 Têxtil, vestuário e algodão | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-01 Corredor de Nacala e Norte | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-01 Corredor de Nacala e Norte | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-01 Corredor de Nacala e Norte | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-01 Corredor de Nacala e Norte | CLU-06 Mineração e transformação mineral | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-01 Corredor de Nacala e Norte | CLU-04 Turismo costeiro e economia azul | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-07 Tecnologia e serviços digitais | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-04 Corredor Maputo–Limpopo | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-04 Corredor Maputo–Limpopo | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-05 Corredor Turístico Inhambane–Bazaruto | CLU-07 Tecnologia e serviços digitais | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-05 Corredor Turístico Inhambane–Bazaruto | CLU-04 Turismo costeiro e economia azul | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-03 Corredor Zambeze–Macuse | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-03 Corredor Zambeze–Macuse | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-03 Corredor Zambeze–Macuse | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | -| COR-03 Corredor Zambeze–Macuse | CLU-06 Mineração e transformação mineral | Mapeamento de planeamento registado; relação factual por confirmar. | +| Corredor | Cluster | Resultado da revisão | +| --------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------- | +| COR-02 Corredor da Beira e Centro | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-02 Corredor da Beira e Centro | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-02 Corredor da Beira e Centro | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-02 Corredor da Beira e Centro | CLU-05 Têxtil, vestuário e algodão | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-01 Corredor de Nacala e Norte | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-01 Corredor de Nacala e Norte | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-01 Corredor de Nacala e Norte | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-01 Corredor de Nacala e Norte | CLU-06 Mineração e transformação mineral | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-01 Corredor de Nacala e Norte | CLU-04 Turismo costeiro e economia azul | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-06 Corredor Industrial Nacional Norte–Sul | CLU-07 Tecnologia e serviços digitais | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-04 Corredor Maputo–Limpopo | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-04 Corredor Maputo–Limpopo | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-05 Corredor Turístico Inhambane–Bazaruto | CLU-07 Tecnologia e serviços digitais | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-05 Corredor Turístico Inhambane–Bazaruto | CLU-04 Turismo costeiro e economia azul | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-03 Corredor Zambeze–Macuse | CLU-01 Agro-indústria e segurança alimentar | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-03 Corredor Zambeze–Macuse | CLU-02 Energia e industrialização | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-03 Corredor Zambeze–Macuse | CLU-03 Logística, corredores e exportação | Mapeamento de planeamento registado; relação factual por confirmar. | +| COR-03 Corredor Zambeze–Macuse | CLU-06 Mineração e transformação mineral | Mapeamento de planeamento registado; relação factual por confirmar. | Os outros 30 vínculos (`cluster_projects`: 13; `corridor_projects`: 17) têm os mesmos campos de validação e prova vazios. A sua lista exacta é reproduzível em `InvestmentPortfolioSeeder`: as âncoras são extraídas dos campos `anchors` do JSON de importação e das matrizes de COR-01 a COR-06. Não foi localizada evidência individual; nenhum deles foi validado. ## Projectos âncora — estado documental observado -| Código | Projecto | Estado de execução | Orçamento/calendário | Fonte e decisão | -|---|---|---|---|---| -| A01 | MozParks – 20 Parques Industriais | draft | vazio | Fonte genérica do documento de programação, secções 6.1/6.6; situação operacional não confirmada. | -| A02 | Linha Ferroviária Norte–Sul | draft | vazio | Idem; fonte primária individual por confirmar. | -| A03 | Programa de Desenvolvimento Integrado do Corredor de Nacala | draft | vazio | Idem; financiamento, calendário e execução por confirmar. | -| A04 | Crusse Jamali – Zona Económica Especial de Turismo | draft | vazio | Idem; estatuto da zona e disponibilidade por confirmar. | -| A05 | Linha de Transmissão Namialo–Metoro (400 kV) | draft | vazio | Idem; fonte e calendário por confirmar. | -| A06 | Cadeia de Valor do Arroz por Projecto de Processamento | draft | vazio | Idem; promotor e projecto executável por confirmar. | -| A07 | Parque Têxtil Integrado | draft | vazio | Idem; estatuto, infra-estruturas e disponibilidade por confirmar. | -| A08 | Zona Económica Especial de Mocuba | draft | vazio | Idem; diploma, operador e condições por confirmar. | -| A09 | Construção da Cidadela Parlamentar de Mocuba | draft | vazio | Idem; entidade, projecto e calendário por confirmar. | -| A10 | Mphanda Nkuwa HPP – Hidroeléctrica | draft | vazio | Idem; fase, fonte individual e calendário por confirmar. | -| A11 | Corredor Ferroviário e Portuário de Macuse–Moatize | draft | vazio | Idem; fase, financiamento e calendário por confirmar. | -| A12 | Programa de Conectividade e Resiliência Costeira (Compacto II) | draft | vazio | Idem; designação/fase e fonte primária por confirmar. | -| A13 | Vilanculos – Região Integrada de Turismo | draft | vazio | Idem; promotor, estatuto e disponibilidade por confirmar. | -| A14 | Zona Económica Especial de Agronegócio do Limpopo | draft | vazio | Idem; diploma, operador e condições por confirmar. | -| A15 | Regadio na Bacia Hidrográfica do Rio Maputo | draft | vazio | Idem; entidade, calendário e disponibilidade por confirmar. | +| Código | Projecto | Estado de execução | Orçamento/calendário | Fonte e decisão | +| ------ | -------------------------------------------------------------- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------- | +| A01 | MozParks – 20 Parques Industriais | draft | vazio | Fonte genérica do documento de programação, secções 6.1/6.6; situação operacional não confirmada. | +| A02 | Linha Ferroviária Norte–Sul | draft | vazio | Idem; fonte primária individual por confirmar. | +| A03 | Programa de Desenvolvimento Integrado do Corredor de Nacala | draft | vazio | Idem; financiamento, calendário e execução por confirmar. | +| A04 | Crusse Jamali – Zona Económica Especial de Turismo | draft | vazio | Idem; estatuto da zona e disponibilidade por confirmar. | +| A05 | Linha de Transmissão Namialo–Metoro (400 kV) | draft | vazio | Idem; fonte e calendário por confirmar. | +| A06 | Cadeia de Valor do Arroz por Projecto de Processamento | draft | vazio | Idem; promotor e projecto executável por confirmar. | +| A07 | Parque Têxtil Integrado | draft | vazio | Idem; estatuto, infra-estruturas e disponibilidade por confirmar. | +| A08 | Zona Económica Especial de Mocuba | draft | vazio | Idem; diploma, operador e condições por confirmar. | +| A09 | Construção da Cidadela Parlamentar de Mocuba | draft | vazio | Idem; entidade, projecto e calendário por confirmar. | +| A10 | Mphanda Nkuwa HPP – Hidroeléctrica | draft | vazio | Idem; fase, fonte individual e calendário por confirmar. | +| A11 | Corredor Ferroviário e Portuário de Macuse–Moatize | draft | vazio | Idem; fase, financiamento e calendário por confirmar. | +| A12 | Programa de Conectividade e Resiliência Costeira (Compacto II) | draft | vazio | Idem; designação/fase e fonte primária por confirmar. | +| A13 | Vilanculos – Região Integrada de Turismo | draft | vazio | Idem; promotor, estatuto e disponibilidade por confirmar. | +| A14 | Zona Económica Especial de Agronegócio do Limpopo | draft | vazio | Idem; diploma, operador e condições por confirmar. | +| A15 | Regadio na Bacia Hidrográfica do Rio Maputo | draft | vazio | Idem; entidade, calendário e disponibilidade por confirmar. | ## Ensaios e evidências @@ -72,4 +72,3 @@ Os outros 30 vínculos (`cluster_projects`: 13; `corridor_projects`: 17) têm os 2. Fonte primária e estado actual para cada um dos 15 projectos âncora; decisão humana de publicação/actualização após confronto documental. Nenhum estado de concurso é inferido. 3. Para zonas, fornecer fonte legal validada e registo real autorizado para ensaio visual de ponta a ponta. O fluxo sintético prova implementação técnica, não conteúdo real. 4. Para investimentos, substituir ou validar os seis registos de seed; fornecer ao menos uma oportunidade e um anúncio de negócio autorizados, com fonte e data, antes de ensaiar uma pretensão real. O fluxo de submissão foi coberto por fixtures na suite, sem criar dados reais. - diff --git a/docs/investmoz/catalogues/openapi.json b/docs/investmoz/catalogues/openapi.json index 6d09c56d..999e07fe 100644 --- a/docs/investmoz/catalogues/openapi.json +++ b/docs/investmoz/catalogues/openapi.json @@ -1,128 +1,256 @@ { - "openapi": "3.1.0", - "info": { - "title": "InvestMoz reference catalogues", - "version": "1.0.0", - "description": "Read-only, rate-limited reference options with stable codes, version metadata and pt-MZ labels. Jurisdictions use the packaged territorial reference set. Economic activities are portal-curated sector groupings, not an official statutory classification. Authority entries are withheld until an authoritative source is approved." - }, - "servers": [{ "url": "/api/v1/catalogs" }], - "security": [], - "paths": { - "/authorities": { "$ref": "#/components/pathItems/Authorities" }, - "/jurisdictions": { "$ref": "#/components/pathItems/Jurisdictions" }, - "/economic-activities": { "$ref": "#/components/pathItems/EconomicActivities" } - }, - "components": { - "pathItems": { - "Authorities": { - "get": { - "summary": "Search the published authorities catalogue", - "parameters": [ - { "$ref": "#/components/parameters/q" }, { "$ref": "#/components/parameters/version" }, - { "$ref": "#/components/parameters/page" }, { "$ref": "#/components/parameters/per_page" }, - { "name": "jurisdiction_id", "in": "query", "schema": { "type": "string", "format": "uuid" } } - ], - "responses": { "200": { "$ref": "#/components/responses/CatalogPage" }, "429": { "description": "Rate limit exceeded" } } - } - }, - "Jurisdictions": { - "get": { - "summary": "List territorial reference options for dependent dropdowns", - "parameters": [ - { "$ref": "#/components/parameters/q" }, { "$ref": "#/components/parameters/version" }, - { "$ref": "#/components/parameters/page" }, { "$ref": "#/components/parameters/per_page" }, - { "name": "type", "in": "query", "required": true, "schema": { "type": "string", "enum": ["country", "province", "district", "admin_post", "locality"] } }, - { "name": "parent_id", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "Required for district, admin_post and locality; the parent must be of the immediately preceding level." } - ], - "responses": { "200": { "$ref": "#/components/responses/CatalogPage" }, "422": { "description": "Invalid or missing parent selection" }, "429": { "description": "Rate limit exceeded" } } - } - }, - "EconomicActivities": { - "get": { - "summary": "Search the portal-curated economic activity groupings", - "parameters": [ - { "$ref": "#/components/parameters/q" }, { "$ref": "#/components/parameters/version" }, - { "$ref": "#/components/parameters/page" }, { "$ref": "#/components/parameters/per_page" }, - { "name": "parent_id", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "Optional parent activity for hierarchical catalog browsing." }, - { "name": "sector_id", "in": "query", "schema": { "type": "integer", "minimum": 1 }, "description": "Optional sector filter for the dependent activity dropdown." } - ], - "responses": { "200": { "$ref": "#/components/responses/CatalogPage" }, "429": { "description": "Rate limit exceeded" } } - } - } + "openapi": "3.1.0", + "info": { + "title": "InvestMoz reference catalogues", + "version": "1.0.0", + "description": "Read-only, rate-limited reference options with stable codes, version metadata and pt-MZ labels. Jurisdictions use the packaged territorial reference set. Economic activities are portal-curated sector groupings, not an official statutory classification. Authority entries are withheld until an authoritative source is approved." }, - "parameters": { - "q": { "name": "q", "in": "query", "schema": { "type": "string", "maxLength": 100 } }, - "version": { "name": "version", "in": "query", "schema": { "type": "integer", "minimum": 1 }, "description": "A published immutable version; omitted selects the current effective version." }, - "page": { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } }, - "per_page": { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } + "servers": [{ "url": "/api/v1/catalogs" }], + "security": [], + "paths": { + "/authorities": { "$ref": "#/components/pathItems/Authorities" }, + "/jurisdictions": { "$ref": "#/components/pathItems/Jurisdictions" }, + "/economic-activities": { + "$ref": "#/components/pathItems/EconomicActivities" + } }, - "responses": { - "CatalogPage": { - "description": "Stable option values with Portuguese (Mozambique) labels. A not-yet-published catalogue returns an empty page and status not_published.", - "headers": { - "Cache-Control": { "schema": { "type": "string" } }, - "ETag": { "schema": { "type": "string" } } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["data", "links", "meta", "next_page_url", "prev_page_url", "current_page", "per_page", "total", "last_page"], - "properties": { - "data": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogEntry" } }, - "links": { "type": "object", "properties": { "next": { "type": ["string", "null"] }, "previous": { "type": ["string", "null"] } } }, - "next_page_url": { "type": ["string", "null"] }, - "prev_page_url": { "type": ["string", "null"] }, - "current_page": { "type": "integer" }, - "per_page": { "type": "integer" }, - "total": { "type": "integer" }, - "last_page": { "type": "integer" }, - "meta": { "$ref": "#/components/schemas/CatalogMetadata" } - } + "components": { + "pathItems": { + "Authorities": { + "get": { + "summary": "Search the published authorities catalogue", + "parameters": [ + { "$ref": "#/components/parameters/q" }, + { "$ref": "#/components/parameters/version" }, + { "$ref": "#/components/parameters/page" }, + { "$ref": "#/components/parameters/per_page" }, + { + "name": "jurisdiction_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/CatalogPage" }, + "429": { "description": "Rate limit exceeded" } + } + } + }, + "Jurisdictions": { + "get": { + "summary": "List territorial reference options for dependent dropdowns", + "parameters": [ + { "$ref": "#/components/parameters/q" }, + { "$ref": "#/components/parameters/version" }, + { "$ref": "#/components/parameters/page" }, + { "$ref": "#/components/parameters/per_page" }, + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": [ + "country", + "province", + "district", + "admin_post", + "locality" + ] + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" }, + "description": "Required for district, admin_post and locality; the parent must be of the immediately preceding level." + } + ], + "responses": { + "200": { "$ref": "#/components/responses/CatalogPage" }, + "422": { + "description": "Invalid or missing parent selection" + }, + "429": { "description": "Rate limit exceeded" } + } + } + }, + "EconomicActivities": { + "get": { + "summary": "Search the portal-curated economic activity groupings", + "parameters": [ + { "$ref": "#/components/parameters/q" }, + { "$ref": "#/components/parameters/version" }, + { "$ref": "#/components/parameters/page" }, + { "$ref": "#/components/parameters/per_page" }, + { + "name": "parent_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" }, + "description": "Optional parent activity for hierarchical catalog browsing." + }, + { + "name": "sector_id", + "in": "query", + "schema": { "type": "integer", "minimum": 1 }, + "description": "Optional sector filter for the dependent activity dropdown." + } + ], + "responses": { + "200": { "$ref": "#/components/responses/CatalogPage" }, + "429": { "description": "Rate limit exceeded" } + } + } + } + }, + "parameters": { + "q": { + "name": "q", + "in": "query", + "schema": { "type": "string", "maxLength": 100 } + }, + "version": { + "name": "version", + "in": "query", + "schema": { "type": "integer", "minimum": 1 }, + "description": "A published immutable version; omitted selects the current effective version." + }, + "page": { + "name": "page", + "in": "query", + "schema": { "type": "integer", "minimum": 1 } + }, + "per_page": { + "name": "per_page", + "in": "query", + "schema": { "type": "integer", "minimum": 1, "maximum": 100 } + } + }, + "responses": { + "CatalogPage": { + "description": "Stable option values with Portuguese (Mozambique) labels. A not-yet-published catalogue returns an empty page and status not_published.", + "headers": { + "Cache-Control": { "schema": { "type": "string" } }, + "ETag": { "schema": { "type": "string" } } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data", + "links", + "meta", + "next_page_url", + "prev_page_url", + "current_page", + "per_page", + "total", + "last_page" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CatalogEntry" + } + }, + "links": { + "type": "object", + "properties": { + "next": { "type": ["string", "null"] }, + "previous": { + "type": ["string", "null"] + } + } + }, + "next_page_url": { "type": ["string", "null"] }, + "prev_page_url": { "type": ["string", "null"] }, + "current_page": { "type": "integer" }, + "per_page": { "type": "integer" }, + "total": { "type": "integer" }, + "last_page": { "type": "integer" }, + "meta": { + "$ref": "#/components/schemas/CatalogMetadata" + } + } + } + } + } + } + }, + "schemas": { + "CatalogEntry": { + "type": "object", + "required": ["id", "value", "code", "name", "label", "locale"], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "value": { + "type": "string", + "description": "Stable API value; never the translated label." + }, + "code": { "type": "string" }, + "name": { "type": "string" }, + "label": { + "type": "string", + "description": "Presentation label in pt-MZ." + }, + "locale": { "const": "pt-MZ" }, + "parent_id": { + "type": ["string", "null"], + "format": "uuid" + }, + "type": { "type": "string" }, + "version_no": { "type": "integer", "minimum": 1 }, + "sector_id": { "type": "integer" }, + "source_code": { "type": ["string", "null"] }, + "authority_type": { "type": "string" }, + "organization_id": { "type": "string", "format": "uuid" }, + "jurisdiction_id": { + "type": ["string", "null"], + "format": "uuid" + }, + "jurisdiction_name": { "type": ["string", "null"] } + } + }, + "CatalogMetadata": { + "type": "object", + "required": [ + "catalog", + "version", + "locale", + "current_page", + "per_page", + "total", + "last_page" + ], + "properties": { + "catalog": { + "type": "string", + "enum": [ + "authorities", + "jurisdictions", + "economic-activities" + ] + }, + "version": { "type": ["integer", "null"] }, + "locale": { "const": "pt-MZ" }, + "status": { "type": "string", "enum": ["not_published"] }, + "source_version": { "type": ["string", "null"] }, + "valid_from": { + "type": ["string", "null"], + "format": "date" + }, + "valid_to": { + "type": ["string", "null"], + "format": "date" + }, + "current_page": { "type": "integer" }, + "per_page": { "type": "integer" }, + "total": { "type": "integer" }, + "last_page": { "type": "integer" } + } } - } } - } - }, - "schemas": { - "CatalogEntry": { - "type": "object", - "required": ["id", "value", "code", "name", "label", "locale"], - "properties": { - "id": { "type": "string", "format": "uuid" }, - "value": { "type": "string", "description": "Stable API value; never the translated label." }, - "code": { "type": "string" }, - "name": { "type": "string" }, - "label": { "type": "string", "description": "Presentation label in pt-MZ." }, - "locale": { "const": "pt-MZ" }, - "parent_id": { "type": ["string", "null"], "format": "uuid" }, - "type": { "type": "string" }, - "version_no": { "type": "integer", "minimum": 1 }, - "sector_id": { "type": "integer" }, - "source_code": { "type": ["string", "null"] }, - "authority_type": { "type": "string" }, - "organization_id": { "type": "string", "format": "uuid" }, - "jurisdiction_id": { "type": ["string", "null"], "format": "uuid" }, - "jurisdiction_name": { "type": ["string", "null"] } - } - }, - "CatalogMetadata": { - "type": "object", - "required": ["catalog", "version", "locale", "current_page", "per_page", "total", "last_page"], - "properties": { - "catalog": { "type": "string", "enum": ["authorities", "jurisdictions", "economic-activities"] }, - "version": { "type": ["integer", "null"] }, - "locale": { "const": "pt-MZ" }, - "status": { "type": "string", "enum": ["not_published"] }, - "source_version": { "type": ["string", "null"] }, - "valid_from": { "type": ["string", "null"], "format": "date" }, - "valid_to": { "type": ["string", "null"], "format": "date" }, - "current_page": { "type": "integer" }, - "per_page": { "type": "integer" }, - "total": { "type": "integer" }, - "last_page": { "type": "integer" } - } - } } - } } diff --git a/docs/investmoz/completion-audit-2026-10-04.md b/docs/investmoz/completion-audit-2026-10-04.md index c1394a9e..fdf51c0b 100644 --- a/docs/investmoz/completion-audit-2026-10-04.md +++ b/docs/investmoz/completion-audit-2026-10-04.md @@ -8,24 +8,24 @@ Este relatório é um índice de estado actual; prevalece sobre afirmações his ## Reconciliação por grupos de pedidos -| Grupo | Estado técnico actual | Pendência que impede conclusão | -| --- | --- | --- | -| Diagnóstico integral da aplicação e inventário de funcionalidades | Parcial; documentação/matriz consolidada existe | Rever manualmente todas as páginas e validar com perfis reais. Integrações e comportamentos não comprovados pelo código continuam “a validar”. | -| Migrações, PostgreSQL/PostGIS, segurança e QA | Parcial/concluído tecnicamente em componentes | Suite foi repetida em cluster descartável; o incidente histórico em `investmoz_test:5432` carece de comparação/restauro validado por DBA. Não executar operações destrutivas nessa base. | -| Geografia e formulários de investimento/oportunidades | Implementação extensa e testes presentes | Confirmar catálogo territorial e conteúdo com fonte de negócio, percorrer browser/dispositivos e validar permissões operacionais. | -| Projectos, estruturas de investimento, carteira e catálogo | Parcial | Aprovação institucional dos dados, contratos de submissão e percurso completo de investimento real não demonstrados. Não há processamento/custódia de investimento financeiro confirmado. | -| Portal público de manifestações e CRM local | Recepção e conversão local idempotentes com consentimento | Fluxo é local. Processamento externo, sincronização e reconciliação não activos. | -| SuiteCRM | Cliente API OAuth implementado e desligado por defeito; testes simulados; catálogo lógico/matriz com 15 colunas criado em `suitecrm/mapping-catalog.md` | Docker/Podman ausentes e WSL não instalado; a release SuiteCRM 8.10.x consultada só lista PHP 8.2–8.4, enquanto o projecto exige PHP 8.5. Instalação, campos, RBAC, workers, reconciliação, segurança e operação aguardam runtime compatível/homologação. `SUITECRM_ENABLED=false` deve manter-se. | -| Simulador financeiro, plano e tesouraria | Calculadoras independentes implementadas e cobertas por testes | Validação contabilística/institucional e teste de utilizadores. Estimativas não são ofertas de crédito ou aconselhamento. | -| Motor fiscal e reforma 2026 | DSL/governação e resolvedor técnicos parciais; componentes legais permanecem indeterminadas | Texto oficial consolidado, anexos, vigência, fundamentos, parâmetros aprovados e casos dourados assinados por Fiscalista. Não activar taxas nem declarar apuramento. | -| IVA e rectificação da Lei 13/2016 | Investigação documental parcial; cópia local do BR 89/2017 tem proveniência incompleta; digitalização rectificada não foi lida com fiabilidade | Confirmar autenticidade, texto integral dos anexos corrigidos, vigência e consolidação em fonte oficial. Nenhuma regra decorrente foi activada. | -| Incentivos, ISPC, IRPS/IRPC, INSS, retenções, ICE e pauta aduaneira | Estrutura permite manter resultados indeterminados | Validação legal por regime/período, fontes oficiais integrais, elegibilidade/classificação e aprovação profissional. | -| Educação e catálogo de serviços | Funcionalidade implementada parcialmente e testes existentes | Aprovação institucional dos cursos, prestadores, reconhecimento e correspondência a emprego. | -| Importação/revisão de dados e oportunidades | Revisões documentais e staging existentes; alguns registos foram deixados privados | Resolver duplicados, moedas e autoridade de origem com os responsáveis; não publicar dados incertos. | -| Dados pessoais/PII | Revisão e controlos de snapshots, consentimento e minimização existentes | Confirmar finalidade, retenção, acesso, eliminação, bases legais e transferência com encarregado/jurídico; revisão completa de ficheiros de entrada. | -| Formulário e acompanhamento de assistência fundiária/DUAT | Fluxo e testes implementados | Não substitui pedido ou decisão oficial. Rever base legal, documentos, acesso, retenção, integração externa e percursos com equipa responsável. | -| Acessibilidade e experiência | Testes unitários, browser/visual e validações responsivas registadas para alguns percursos | Teste presencial com teclado, leitor de ecrã, dispositivos e utilizadores; rever mensagens, erros, carregamento e todos os percursos. | -| Entrega/produção | Não concluído | Sem autorização de publicação, integração real, migração ou activação. Requer revisão humana e plano de operação/rollback. | +| Grupo | Estado técnico actual | Pendência que impede conclusão | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Diagnóstico integral da aplicação e inventário de funcionalidades | Parcial; documentação/matriz consolidada existe | Rever manualmente todas as páginas e validar com perfis reais. Integrações e comportamentos não comprovados pelo código continuam “a validar”. | +| Migrações, PostgreSQL/PostGIS, segurança e QA | Parcial/concluído tecnicamente em componentes | Suite foi repetida em cluster descartável; o incidente histórico em `investmoz_test:5432` carece de comparação/restauro validado por DBA. Não executar operações destrutivas nessa base. | +| Geografia e formulários de investimento/oportunidades | Implementação extensa e testes presentes | Confirmar catálogo territorial e conteúdo com fonte de negócio, percorrer browser/dispositivos e validar permissões operacionais. | +| Projectos, estruturas de investimento, carteira e catálogo | Parcial | Aprovação institucional dos dados, contratos de submissão e percurso completo de investimento real não demonstrados. Não há processamento/custódia de investimento financeiro confirmado. | +| Portal público de manifestações e CRM local | Recepção e conversão local idempotentes com consentimento | Fluxo é local. Processamento externo, sincronização e reconciliação não activos. | +| SuiteCRM | Cliente API OAuth implementado e desligado por defeito; testes simulados; catálogo lógico/matriz com 15 colunas criado em `suitecrm/mapping-catalog.md` | Docker/Podman ausentes e WSL não instalado; a release SuiteCRM 8.10.x consultada só lista PHP 8.2–8.4, enquanto o projecto exige PHP 8.5. Instalação, campos, RBAC, workers, reconciliação, segurança e operação aguardam runtime compatível/homologação. `SUITECRM_ENABLED=false` deve manter-se. | +| Simulador financeiro, plano e tesouraria | Calculadoras independentes implementadas e cobertas por testes | Validação contabilística/institucional e teste de utilizadores. Estimativas não são ofertas de crédito ou aconselhamento. | +| Motor fiscal e reforma 2026 | DSL/governação e resolvedor técnicos parciais; componentes legais permanecem indeterminadas | Texto oficial consolidado, anexos, vigência, fundamentos, parâmetros aprovados e casos dourados assinados por Fiscalista. Não activar taxas nem declarar apuramento. | +| IVA e rectificação da Lei 13/2016 | Investigação documental parcial; cópia local do BR 89/2017 tem proveniência incompleta; digitalização rectificada não foi lida com fiabilidade | Confirmar autenticidade, texto integral dos anexos corrigidos, vigência e consolidação em fonte oficial. Nenhuma regra decorrente foi activada. | +| Incentivos, ISPC, IRPS/IRPC, INSS, retenções, ICE e pauta aduaneira | Estrutura permite manter resultados indeterminados | Validação legal por regime/período, fontes oficiais integrais, elegibilidade/classificação e aprovação profissional. | +| Educação e catálogo de serviços | Funcionalidade implementada parcialmente e testes existentes | Aprovação institucional dos cursos, prestadores, reconhecimento e correspondência a emprego. | +| Importação/revisão de dados e oportunidades | Revisões documentais e staging existentes; alguns registos foram deixados privados | Resolver duplicados, moedas e autoridade de origem com os responsáveis; não publicar dados incertos. | +| Dados pessoais/PII | Revisão e controlos de snapshots, consentimento e minimização existentes | Confirmar finalidade, retenção, acesso, eliminação, bases legais e transferência com encarregado/jurídico; revisão completa de ficheiros de entrada. | +| Formulário e acompanhamento de assistência fundiária/DUAT | Fluxo e testes implementados | Não substitui pedido ou decisão oficial. Rever base legal, documentos, acesso, retenção, integração externa e percursos com equipa responsável. | +| Acessibilidade e experiência | Testes unitários, browser/visual e validações responsivas registadas para alguns percursos | Teste presencial com teclado, leitor de ecrã, dispositivos e utilizadores; rever mensagens, erros, carregamento e todos os percursos. | +| Entrega/produção | Não concluído | Sem autorização de publicação, integração real, migração ou activação. Requer revisão humana e plano de operação/rollback. | ## Verificações executadas nesta revisão diff --git a/docs/investmoz/consolidated-review-matrix-2026-10-03.md b/docs/investmoz/consolidated-review-matrix-2026-10-03.md index 88173c8b..01cd5a03 100644 --- a/docs/investmoz/consolidated-review-matrix-2026-10-03.md +++ b/docs/investmoz/consolidated-review-matrix-2026-10-03.md @@ -4,74 +4,74 @@ Data: 2026-10-03. Âmbito: prompt master recebido em `Pasted text.txt`, document ## Estado inicial e ambiente -| Item | Evidência observada | -| --- | --- | -| Branch / commit | `main` / `4f35313d06ccd597a4ebf9ed043964d898c95813` | -| Árvore de trabalho | 50 ficheiros rastreados modificados, muitos não rastreados; cerca de 7.748 inserções e 1.053 remoções rastreadas. Estado anterior preservado, sem reset ou limpeza. | -| Runtime | PHP 8.5.11, Composer 2.8.12, Node 24.20.0, npm 11.19.0. Laravel 13.30.1, Inertia Laravel 3.3.3, React 19.2.8, TypeScript 5.9.3, Vite 8.2.2, PHPUnit 12.5.34. | -| Dados | Configuração local aponta PostgreSQL em `127.0.0.1:5432`; listener activo. O harness PHPUnit usa PostgreSQL/PostGIS na porta 55441 e allowlist de bases QA. Não foi executada suite PHP nesta linha de base por não haver serviço QA 55441 disponível e por não ser seguro apontar testes com `RefreshDatabase` à instância local activa. | -| API / CI | Rotas web e `api/v1`; contratos em `api/openapi.yaml` e contratos por domínio em `docs/investmoz/**`. Workflow `.github/workflows/tests.yml` configura PHP 8.5, Node 24 e serviço PostGIS 17-3.5. | -| Documentos pedidos | Prompt master recebido como anexo e `Prompt master para revisão do potal.docx`, documentos em `Formularios` e `por executar`, e relatório PII estão acessíveis. Não localizei `revisao-prompts-investmoz.md` nem `prototipo-portal-v2_1.html` no caminho pesquisado. A falta desses caminhos não prova ausência de funcionalidades. | +| Item | Evidência observada | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Branch / commit | `main` / `4f35313d06ccd597a4ebf9ed043964d898c95813` | +| Árvore de trabalho | 50 ficheiros rastreados modificados, muitos não rastreados; cerca de 7.748 inserções e 1.053 remoções rastreadas. Estado anterior preservado, sem reset ou limpeza. | +| Runtime | PHP 8.5.11, Composer 2.8.12, Node 24.20.0, npm 11.19.0. Laravel 13.30.1, Inertia Laravel 3.3.3, React 19.2.8, TypeScript 5.9.3, Vite 8.2.2, PHPUnit 12.5.34. | +| Dados | Configuração local aponta PostgreSQL em `127.0.0.1:5432`; listener activo. O harness PHPUnit usa PostgreSQL/PostGIS na porta 55441 e allowlist de bases QA. Não foi executada suite PHP nesta linha de base por não haver serviço QA 55441 disponível e por não ser seguro apontar testes com `RefreshDatabase` à instância local activa. | +| API / CI | Rotas web e `api/v1`; contratos em `api/openapi.yaml` e contratos por domínio em `docs/investmoz/**`. Workflow `.github/workflows/tests.yml` configura PHP 8.5, Node 24 e serviço PostGIS 17-3.5. | +| Documentos pedidos | Prompt master recebido como anexo e `Prompt master para revisão do potal.docx`, documentos em `Formularios` e `por executar`, e relatório PII estão acessíveis. Não localizei `revisao-prompts-investmoz.md` nem `prototipo-portal-v2_1.html` no caminho pesquisado. A falta desses caminhos não prova ausência de funcionalidades. | ## Matriz única Estados: **implementado e verificado**, **parcial**, **não implementado**, **não verificado**, **bloqueado**, **substituído por decisão documentada**. -| ID / origem | Requisito e aceite verificável | Implementação / evidência actual | Estado / lacuna | Dependências / prioridade | Próxima alteração ou teste / validação pendente | -| --- | --- | --- | --- | --- | --- | -| M-01 §1–3, master | Preservar autorização, tecnologia, trabalho existente e limites de publicação/dados. | Branch e commit registados; Laravel 13 + React/Inertia/TS + PostgreSQL/PostGIS confirmados; diff preexistente mantido. | Implementado e verificado no procedimento desta revisão. | Transversal / crítica. | Continuar a executar mudanças reversíveis; nenhuma publicação ou integração real sem configuração e autorização específicas. | -| M-02 §2, master | Reconciliar documentos e caminhos sem inferir ausência funcional. | Prompt master, directórios `Formularios`/`por executar`, relatório PII e docs internos localizados. Revisão consolidada Markdown e protótipo HTML não localizados no caminho pesquisado. | Parcial. | Inventário documental / alta. | Extrair conteúdo/hash dos DOCX relevantes por lote e mapear requisitos aos IDs; confirmar se os dois artefactos ausentes existem noutra pasta. | -| M-03 §4 e §13, master | Inventariar e manter matriz completa, sequencial e persistente. | Esta matriz e o ledger `simulator-evolution-progress.md`; rotas, testes, migrations, CI e OpenAPI localizados. | Parcial. | Todas as etapas / crítica. | Expandir matriz com requisitos atómicos e referências de parágrafo/tabela dos DOCX; actualizar estado após cada etapa. | -| M-04 §5, master | Resolver conflitos documentais sem executar prompts/DDL literalmente. | Decisões reconciliadas em `docs/investmoz/simulations/current-status-2026-10-03.md`, `fiscal-reform-2026-review.md`, `official-source-register.md`, docs GIS e existing-business. | Parcial. | Evidência e decisões do utilizador / alta. | Confirmar precedência documental por domínio e registar decisão quando versões de formulário divergem. | -| M-05 §6 e §14, master | Validar segurança, relações, rascunhos, concorrência, permissões, exposição, erros, pesquisa e UX. | Policies, migrations, formulários, testes de concorrência e fluxo de rascunhos; relatórios browser em alguns domínios. | Parcial; cobertura transversal não verificada. | Acesso QA, critérios por domínio / crítica. | Testes negativos de autorização, erros, pesquisa/paginação, concorrência e acessibilidade para cada fluxo antes de aceite. | -| D-01 §7, master | Dados mestres territoriais consistentes, acessíveis e pesquisáveis. | `PublicGeographyController`, `TerritorialRegistry`, migrations/seeder e `docs/investmoz/gis`; teste frontend limpa descendentes quando ascendente muda. | Parcial. | Fontes oficiais, integridade hierárquica / alta. | Confirmar cobertura de países/CAE/localidades, pesquisa sem acentos, pertença servidor e reconciliação de importação sem fusão fuzzy automática. | -| D-02 §8, master | Projectos, clusters, corredores, parques, ZEE/ZFI e PII com fonte, relação e revisão. | Controllers e rotas para projectos/clusters/zonas; documentos de zonas e baseline; relatório PII DOCX inventariado em `pii-extraction-review-2026-10-03.md`. | Parcial. Corredores/parques e importação rastreável do PII precisam de auditoria funcional específica. | Origem, aprovação editorial, relações versionadas / alta. | 174 linhas de projecto inventariadas com tabelas de origem; um título repetido e uma tabela com moeda USD/MZN ambígua. Staging CSV aguarda reconciliação do duplicado/moeda e validação responsável/localização; sem publicar projectos previstos como oportunidades. | -| D-03 §9, master | Catálogo consistente e formulários curtos, condicionais, retomáveis, com dados públicos separados e submissão não publica automaticamente. | Catálogo público, intake de empresas existentes, formulários de investidor/simulador e rascunhos; `docs/investmoz/existing-business/**`. | Parcial. Upload seguro/quarentena e reconciliação completa mapa/lista/contagens não verificados. | RBAC, armazenamento privado, política antimalware / crítica. | Fazer threat review e testes de formato/tamanho/autorização/revogação; anexos pendentes não devem ser distribuídos. | -| D-04 §10, master | CRM interno com interesses, contactos, responsáveis, tarefas, consentimento e acompanhamento; SuiteCRM por API suportada. | `CrmController`, modelos/rotas management, consentimento e documentação `docs/investmoz/suitecrm/**`; testes de intake e cliente. | Parcial. Mandatos, diligência, propostas versionadas, fecho e integração real não verificados ou ausentes. | Papéis, consentimento, configuração SuiteCRM / alta. | Cobrir revogação e isolamento por organização; manter CRM interno distinto; não activar SuiteCRM nem tratar aceite como transacção concluída. | -| D-05 §11, master | Fontes legais versionadas; simulador com pressupostos e histórico; cálculo fiscal apenas com fontes/regras aprovadas. | Governança de regras, contratos, snapshots, fontes arquivadas e testes; `current-status-2026-10-03.md` mantém cálculo fiscal público bloqueado. | Parcial; cálculo fiscal bloqueado. | Fiscalista, diplomas integrais e parâmetros vigentes / crítica. | Manter indeterminação; não activar taxas/benefícios; validar alterações por período e cenários financeiros independentes. | -| D-06 §12, master | Educação baseada em evidência, sem certificação/contratação automática; assistência com fontes autorizadas e revisão. | Controllers/páginas/testes Educação; AiGateway e controles internos documentados. | Parcial. Cobertura de correspondência oferta/procura e revalidação das fontes precisa aceite funcional. | Dados autorizados, moderação humana / média-alta. | Auditar permissões, fonte e decisão humana; não introduzir modelo generativo nem recomendação automática sem gate. | -| D-07 §13, master | Executar sequência dependente sem parar após plano; guardar estado e bloqueios por módulo. | Etapas documentadas em gates 9–11 e ledger; várias etapas já parcialmente executadas. | Parcial. | Matriz + validação por módulo / alta. | Continuar com segurança/integridade e módulos independentes enquanto gates legal/external ficam bloqueados. | -| M-08 §14–15, master | Verificações reproduzíveis, comparação com base, sem alegar sucesso global. | CI existe; evidências PHP 231/231. Nesta linha: frontend 99/99, TypeScript passou e build passou; PHPStan 0 erros. Pint e `vp check` globais mantêm achados descritos abaixo. | Parcial. A cobertura manual com leitor de ecrã permanece em falta. | QA PostGIS isolado / crítica. | Corrigir lint legado/ledger sem reescrita não autorizada; completar validação assistiva manual. | -| M-09 §16, master | Relatório final com evidência, reconciliação, rollback e prontidão por módulo. | Matriz inicial criada; runbooks e status parciais por domínio. | Parcial. | Conclusão das etapas / média. | Actualizar tabela e prontidão no fecho de cada tranche; não declarar lançamento integral antes de gates. | -| C-01 regra de conflito, master | Não fabricar coordenadas; manter lista para não georreferenciados. | Fluxos de coordenadas e mapas existentes; documentação GIS prevê revisão de localização. | Não verificado ponta a ponta. | Fonte espacial validada / alta. | Teste de oportunidade sem coordenadas na lista e não inclusão no mapa; verificar contagens. | -| C-02 regra de conflito, master | Distinguir CRM interno de SuiteCRM e domínios de projecto/dossier/oportunidade/negócio/território. | Rotas, modelos e contratos separados; SuiteCRM tem cliente/outbox documentados. | Parcial. | Dicionário canónico / alta. | Rever imports, conversões e UI para impedir associação/tipo implícito indevido. | -| C-03 regra de conflito, master | Aprovações próprias de cada domínio; não aplicar DDL histórico se estrutura equivalente existir. | Gates de domínio, migrations incrementais e documentação de esquema existentes. | Parcial. | Mapeamento migrations/modelos/gates / alta. | Validar cada migration contra schema real QA e manter as migrations executadas imutáveis. | -| V-01 relatório PII + §8 master | Extrair investimento, período, moeda, fase, responsáveis e localização com origem verificável. | DOCX identificado por SHA-256 e estruturalmente lido; resumo em `pii-extraction-review-2026-10-03.md`; nenhuma importação executada. | Parcial; estrutura e contagens verificadas, conteúdo substancial não validado. | Confirmar duplicado, moeda da tabela 19, responsável, estado e topónimos / alta. | Produzir staging rastreável apenas depois da revisão humana; sem publicar como oportunidade. | -| V-02 §9 master | Upload/download protegido: MIME real, tamanho, classe, storage privado, ACL, antimalware e histórico. | `PortalServicesController::upload` passou a gravar em `quarantined/internal`; o catálogo e `resource()` só aceitam `active/public`; teste actualizado cobre bloqueio e fluxo de documento previamente revisto. | Parcial; a quarentena segura foi corrigida, scanner/fluxo de revisão autorizado não existe/verificado. | Configuração de storage/scanner e perfis / crítica. | Manter novos anexos indisponíveis até scanner + revisão autorizada; teste de integração pendente por falta de PostgreSQL QA activo. | -| V-03 §10 master | Consentimento revogável e acompanhamento sem custódia/pagamentos. | Rotas CRM e retirada de consentimento de divulgação do proponente; sem evidência neste inventário de custódia/pagamento. | Parcial. | Política de consentimento, estado de negociação / alta. | Cobrir revogação com acesso posterior negado e auditoria sem PII; manter pagamentos fora do âmbito. | -| V-04 §11 master + reforma 2026 | Separar períodos fiscais históricos e novos, distinguir crédito de caixa, sem residência inferida por nacionalidade/dias. | Testes frontend de intake fiscal e ponte financeira; status fiscal indeterminado sem regra aprovada. | Parcial; regras legais bloqueadas. | Diploma integral, aprovação fiscal e testes de fronteira / crítica. | Manter estados indeterminados e testar antes/depois de vigência somente quando regra oficial estiver aprovada. | -| V-05 §12 master | Educação e IA apenas com dados autorizados, fontes revalidadas e revisão humana. | Educação e AI gateway com controle de feature; testes locais existem. | Parcial; activação externa não verificada. | Dados e configuração, avaliação humana / alta. | Testar feature desligada, ACL e fontes revogadas; integração permanece desactivada até configuração/autorização. | -| B-01 baseline 2026-10-03 | Checks seguros disponíveis sem alterar dados operacionais. | Nesta extensão: frontend `node --test tests/frontend/*.test.mjs` 99/99; TypeScript passou; build 2.388 módulos; formatter/lint dirigidos passaram. Última suite PHP global permanece 231/231, 2.299 assertions. `composer run lint:check` global falhou em cinco ficheiros anteriores à tranche; `npm.cmd run check` global falhou ao ler o ledger histórico. | Parcial. | QA isolado para novas alterações backend e aceitação final / alta. | Corrigir causa do ledger binário por migração de formato separada; manter histórico append-only. Corrigir lint global em tranche identificada. | +| ID / origem | Requisito e aceite verificável | Implementação / evidência actual | Estado / lacuna | Dependências / prioridade | Próxima alteração ou teste / validação pendente | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| M-01 §1–3, master | Preservar autorização, tecnologia, trabalho existente e limites de publicação/dados. | Branch e commit registados; Laravel 13 + React/Inertia/TS + PostgreSQL/PostGIS confirmados; diff preexistente mantido. | Implementado e verificado no procedimento desta revisão. | Transversal / crítica. | Continuar a executar mudanças reversíveis; nenhuma publicação ou integração real sem configuração e autorização específicas. | +| M-02 §2, master | Reconciliar documentos e caminhos sem inferir ausência funcional. | Prompt master, directórios `Formularios`/`por executar`, relatório PII e docs internos localizados. Revisão consolidada Markdown e protótipo HTML não localizados no caminho pesquisado. | Parcial. | Inventário documental / alta. | Extrair conteúdo/hash dos DOCX relevantes por lote e mapear requisitos aos IDs; confirmar se os dois artefactos ausentes existem noutra pasta. | +| M-03 §4 e §13, master | Inventariar e manter matriz completa, sequencial e persistente. | Esta matriz e o ledger `simulator-evolution-progress.md`; rotas, testes, migrations, CI e OpenAPI localizados. | Parcial. | Todas as etapas / crítica. | Expandir matriz com requisitos atómicos e referências de parágrafo/tabela dos DOCX; actualizar estado após cada etapa. | +| M-04 §5, master | Resolver conflitos documentais sem executar prompts/DDL literalmente. | Decisões reconciliadas em `docs/investmoz/simulations/current-status-2026-10-03.md`, `fiscal-reform-2026-review.md`, `official-source-register.md`, docs GIS e existing-business. | Parcial. | Evidência e decisões do utilizador / alta. | Confirmar precedência documental por domínio e registar decisão quando versões de formulário divergem. | +| M-05 §6 e §14, master | Validar segurança, relações, rascunhos, concorrência, permissões, exposição, erros, pesquisa e UX. | Policies, migrations, formulários, testes de concorrência e fluxo de rascunhos; relatórios browser em alguns domínios. | Parcial; cobertura transversal não verificada. | Acesso QA, critérios por domínio / crítica. | Testes negativos de autorização, erros, pesquisa/paginação, concorrência e acessibilidade para cada fluxo antes de aceite. | +| D-01 §7, master | Dados mestres territoriais consistentes, acessíveis e pesquisáveis. | `PublicGeographyController`, `TerritorialRegistry`, migrations/seeder e `docs/investmoz/gis`; teste frontend limpa descendentes quando ascendente muda. | Parcial. | Fontes oficiais, integridade hierárquica / alta. | Confirmar cobertura de países/CAE/localidades, pesquisa sem acentos, pertença servidor e reconciliação de importação sem fusão fuzzy automática. | +| D-02 §8, master | Projectos, clusters, corredores, parques, ZEE/ZFI e PII com fonte, relação e revisão. | Controllers e rotas para projectos/clusters/zonas; documentos de zonas e baseline; relatório PII DOCX inventariado em `pii-extraction-review-2026-10-03.md`. | Parcial. Corredores/parques e importação rastreável do PII precisam de auditoria funcional específica. | Origem, aprovação editorial, relações versionadas / alta. | 174 linhas de projecto inventariadas com tabelas de origem; um título repetido e uma tabela com moeda USD/MZN ambígua. Staging CSV aguarda reconciliação do duplicado/moeda e validação responsável/localização; sem publicar projectos previstos como oportunidades. | +| D-03 §9, master | Catálogo consistente e formulários curtos, condicionais, retomáveis, com dados públicos separados e submissão não publica automaticamente. | Catálogo público, intake de empresas existentes, formulários de investidor/simulador e rascunhos; `docs/investmoz/existing-business/**`. | Parcial. Upload seguro/quarentena e reconciliação completa mapa/lista/contagens não verificados. | RBAC, armazenamento privado, política antimalware / crítica. | Fazer threat review e testes de formato/tamanho/autorização/revogação; anexos pendentes não devem ser distribuídos. | +| D-04 §10, master | CRM interno com interesses, contactos, responsáveis, tarefas, consentimento e acompanhamento; SuiteCRM por API suportada. | `CrmController`, modelos/rotas management, consentimento e documentação `docs/investmoz/suitecrm/**`; testes de intake e cliente. | Parcial. Mandatos, diligência, propostas versionadas, fecho e integração real não verificados ou ausentes. | Papéis, consentimento, configuração SuiteCRM / alta. | Cobrir revogação e isolamento por organização; manter CRM interno distinto; não activar SuiteCRM nem tratar aceite como transacção concluída. | +| D-05 §11, master | Fontes legais versionadas; simulador com pressupostos e histórico; cálculo fiscal apenas com fontes/regras aprovadas. | Governança de regras, contratos, snapshots, fontes arquivadas e testes; `current-status-2026-10-03.md` mantém cálculo fiscal público bloqueado. | Parcial; cálculo fiscal bloqueado. | Fiscalista, diplomas integrais e parâmetros vigentes / crítica. | Manter indeterminação; não activar taxas/benefícios; validar alterações por período e cenários financeiros independentes. | +| D-06 §12, master | Educação baseada em evidência, sem certificação/contratação automática; assistência com fontes autorizadas e revisão. | Controllers/páginas/testes Educação; AiGateway e controles internos documentados. | Parcial. Cobertura de correspondência oferta/procura e revalidação das fontes precisa aceite funcional. | Dados autorizados, moderação humana / média-alta. | Auditar permissões, fonte e decisão humana; não introduzir modelo generativo nem recomendação automática sem gate. | +| D-07 §13, master | Executar sequência dependente sem parar após plano; guardar estado e bloqueios por módulo. | Etapas documentadas em gates 9–11 e ledger; várias etapas já parcialmente executadas. | Parcial. | Matriz + validação por módulo / alta. | Continuar com segurança/integridade e módulos independentes enquanto gates legal/external ficam bloqueados. | +| M-08 §14–15, master | Verificações reproduzíveis, comparação com base, sem alegar sucesso global. | CI existe; evidências PHP 231/231. Nesta linha: frontend 99/99, TypeScript passou e build passou; PHPStan 0 erros. Pint e `vp check` globais mantêm achados descritos abaixo. | Parcial. A cobertura manual com leitor de ecrã permanece em falta. | QA PostGIS isolado / crítica. | Corrigir lint legado/ledger sem reescrita não autorizada; completar validação assistiva manual. | +| M-09 §16, master | Relatório final com evidência, reconciliação, rollback e prontidão por módulo. | Matriz inicial criada; runbooks e status parciais por domínio. | Parcial. | Conclusão das etapas / média. | Actualizar tabela e prontidão no fecho de cada tranche; não declarar lançamento integral antes de gates. | +| C-01 regra de conflito, master | Não fabricar coordenadas; manter lista para não georreferenciados. | Fluxos de coordenadas e mapas existentes; documentação GIS prevê revisão de localização. | Não verificado ponta a ponta. | Fonte espacial validada / alta. | Teste de oportunidade sem coordenadas na lista e não inclusão no mapa; verificar contagens. | +| C-02 regra de conflito, master | Distinguir CRM interno de SuiteCRM e domínios de projecto/dossier/oportunidade/negócio/território. | Rotas, modelos e contratos separados; SuiteCRM tem cliente/outbox documentados. | Parcial. | Dicionário canónico / alta. | Rever imports, conversões e UI para impedir associação/tipo implícito indevido. | +| C-03 regra de conflito, master | Aprovações próprias de cada domínio; não aplicar DDL histórico se estrutura equivalente existir. | Gates de domínio, migrations incrementais e documentação de esquema existentes. | Parcial. | Mapeamento migrations/modelos/gates / alta. | Validar cada migration contra schema real QA e manter as migrations executadas imutáveis. | +| V-01 relatório PII + §8 master | Extrair investimento, período, moeda, fase, responsáveis e localização com origem verificável. | DOCX identificado por SHA-256 e estruturalmente lido; resumo em `pii-extraction-review-2026-10-03.md`; nenhuma importação executada. | Parcial; estrutura e contagens verificadas, conteúdo substancial não validado. | Confirmar duplicado, moeda da tabela 19, responsável, estado e topónimos / alta. | Produzir staging rastreável apenas depois da revisão humana; sem publicar como oportunidade. | +| V-02 §9 master | Upload/download protegido: MIME real, tamanho, classe, storage privado, ACL, antimalware e histórico. | `PortalServicesController::upload` passou a gravar em `quarantined/internal`; o catálogo e `resource()` só aceitam `active/public`; teste actualizado cobre bloqueio e fluxo de documento previamente revisto. | Parcial; a quarentena segura foi corrigida, scanner/fluxo de revisão autorizado não existe/verificado. | Configuração de storage/scanner e perfis / crítica. | Manter novos anexos indisponíveis até scanner + revisão autorizada; teste de integração pendente por falta de PostgreSQL QA activo. | +| V-03 §10 master | Consentimento revogável e acompanhamento sem custódia/pagamentos. | Rotas CRM e retirada de consentimento de divulgação do proponente; sem evidência neste inventário de custódia/pagamento. | Parcial. | Política de consentimento, estado de negociação / alta. | Cobrir revogação com acesso posterior negado e auditoria sem PII; manter pagamentos fora do âmbito. | +| V-04 §11 master + reforma 2026 | Separar períodos fiscais históricos e novos, distinguir crédito de caixa, sem residência inferida por nacionalidade/dias. | Testes frontend de intake fiscal e ponte financeira; status fiscal indeterminado sem regra aprovada. | Parcial; regras legais bloqueadas. | Diploma integral, aprovação fiscal e testes de fronteira / crítica. | Manter estados indeterminados e testar antes/depois de vigência somente quando regra oficial estiver aprovada. | +| V-05 §12 master | Educação e IA apenas com dados autorizados, fontes revalidadas e revisão humana. | Educação e AI gateway com controle de feature; testes locais existem. | Parcial; activação externa não verificada. | Dados e configuração, avaliação humana / alta. | Testar feature desligada, ACL e fontes revogadas; integração permanece desactivada até configuração/autorização. | +| B-01 baseline 2026-10-03 | Checks seguros disponíveis sem alterar dados operacionais. | Nesta extensão: frontend `node --test tests/frontend/*.test.mjs` 99/99; TypeScript passou; build 2.388 módulos; formatter/lint dirigidos passaram. Última suite PHP global permanece 231/231, 2.299 assertions. `composer run lint:check` global falhou em cinco ficheiros anteriores à tranche; `npm.cmd run check` global falhou ao ler o ledger histórico. | Parcial. | QA isolado para novas alterações backend e aceitação final / alta. | Corrigir causa do ledger binário por migração de formato separada; manter histórico append-only. Corrigir lint global em tranche identificada. | ## Reconciliação de versões e conflitos -| Conflito | Decisão baseada no pedido e evidência actual | Consequência | -| --- | --- | --- | -| Coordenada obrigatória vs localização por validar | Não inventar coordenada; lista continua disponível. | Publicação no mapa requer evidência geográfica validada. | -| CRM vs SuiteCRM | CRM interno é domínio próprio; SuiteCRM é integração por mecanismo suportado. | Sem escrita directa na BD SuiteCRM; manter integração desligada até configuração e teste. | -| Projecto previsto vs oportunidade disponível | São entidades e estados editoriais distintos. | Investimento do PII não entra automaticamente no catálogo público. | -| Benefício/regra fiscal em resumo secundário vs diploma oficial | Somente fonte primária vigente e aprovação formal permitem regra executável. | Cenário público mantém indeterminação quando falta fonte/aprovação. | -| Propostas DDL históricas vs esquema já evoluído | Reconciliar pela estrutura actual e migrations incrementais; não executar DDL literalmente nem editar migration aplicada. | Qualquer divergência requer teste de schema em QA antes da alteração. | +| Conflito | Decisão baseada no pedido e evidência actual | Consequência | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| Coordenada obrigatória vs localização por validar | Não inventar coordenada; lista continua disponível. | Publicação no mapa requer evidência geográfica validada. | +| CRM vs SuiteCRM | CRM interno é domínio próprio; SuiteCRM é integração por mecanismo suportado. | Sem escrita directa na BD SuiteCRM; manter integração desligada até configuração e teste. | +| Projecto previsto vs oportunidade disponível | São entidades e estados editoriais distintos. | Investimento do PII não entra automaticamente no catálogo público. | +| Benefício/regra fiscal em resumo secundário vs diploma oficial | Somente fonte primária vigente e aprovação formal permitem regra executável. | Cenário público mantém indeterminação quando falta fonte/aprovação. | +| Propostas DDL históricas vs esquema já evoluído | Reconciliar pela estrutura actual e migrations incrementais; não executar DDL literalmente nem editar migration aplicada. | Qualquer divergência requer teste de schema em QA antes da alteração. | ## Linha de base e evidências -| Comando executado | Resultado | -| --- | --- | -| `git branch --show-current; git rev-parse HEAD; git status --short` | `main`, commit acima; diff preexistente preservado. | -| `php artisan route:list --json` com PHP 8.5.11 | Sucesso; rotas públicas, autenticadas e management listadas. | -| `composer show --no-interaction --direct` | Sucesso; versões do backend confirmadas. | -| `npm.cmd --version; npm.cmd ls --depth=0 --json` | npm 11.19.0; dependências directas listadas. | -| `node --test tests/frontend/*.test.mjs` | 98 aprovados, 0 falhas. | -| `npm.cmd run types:check` | Passou, `tsc --noEmit`. | -| `composer run types:check --no-interaction` | Passou, PHPStan: 0 erros. | -| `composer run lint:check --no-interaction` | Falhou em cinco ficheiros: migration territorial de 28/09, `routes/investment-zones.php`, `routes/web.php`, `tests/Feature/ApiContractTest.php`, `tools/test-mne-integrity.php`. Não alterados nesta tranche. | -| `npm.cmd run check` | Falhou antes da análise porque `docs/investmoz/simulator-evolution-progress.md` não pode ser lido pelo formatador (ficheiro binário/inacessível). Nenhuma opção `--fix` executada. | -| `php vendor/bin/phpunit --configuration=phpunit.xml --filter PortalServicesTest --do-not-cache-result` | 5/5 testes, 191 assertions; passou na base QA `investmoz_browser_e2e_test`, porta 55441. -| `php vendor/bin/phpunit --configuration=phpunit.xml --do-not-cache-result` | 231/231 testes, 2.299 assertions; passou em 71,593 s na mesma base QA isolada. | -| Build Vite | 2.388 módulos compilados com sucesso nesta revisão. | -| `npm.cmd run build` | Passou nesta revisão; 2.388 módulos. Aviso não bloqueante: dependência opcional `fontaine`. | -| `php -l app/Http/Controllers/PortalServicesController.php`; `php -l tests/Feature/PortalServicesTest.php`; Pint dirigido `--test` nesses dois ficheiros | Sintaxe passou e Pint dirigido passou. | -| `git diff --check` | Passou; avisos apenas de normalização CRLF/LF nos docs/forms preexistentes. | +| Comando executado | Resultado | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `git branch --show-current; git rev-parse HEAD; git status --short` | `main`, commit acima; diff preexistente preservado. | +| `php artisan route:list --json` com PHP 8.5.11 | Sucesso; rotas públicas, autenticadas e management listadas. | +| `composer show --no-interaction --direct` | Sucesso; versões do backend confirmadas. | +| `npm.cmd --version; npm.cmd ls --depth=0 --json` | npm 11.19.0; dependências directas listadas. | +| `node --test tests/frontend/*.test.mjs` | 98 aprovados, 0 falhas. | +| `npm.cmd run types:check` | Passou, `tsc --noEmit`. | +| `composer run types:check --no-interaction` | Passou, PHPStan: 0 erros. | +| `composer run lint:check --no-interaction` | Falhou em cinco ficheiros: migration territorial de 28/09, `routes/investment-zones.php`, `routes/web.php`, `tests/Feature/ApiContractTest.php`, `tools/test-mne-integrity.php`. Não alterados nesta tranche. | +| `npm.cmd run check` | Falhou antes da análise porque `docs/investmoz/simulator-evolution-progress.md` não pode ser lido pelo formatador (ficheiro binário/inacessível). Nenhuma opção `--fix` executada. | +| `php vendor/bin/phpunit --configuration=phpunit.xml --filter PortalServicesTest --do-not-cache-result` | 5/5 testes, 191 assertions; passou na base QA `investmoz_browser_e2e_test`, porta 55441. | +| `php vendor/bin/phpunit --configuration=phpunit.xml --do-not-cache-result` | 231/231 testes, 2.299 assertions; passou em 71,593 s na mesma base QA isolada. | +| Build Vite | 2.388 módulos compilados com sucesso nesta revisão. | +| `npm.cmd run build` | Passou nesta revisão; 2.388 módulos. Aviso não bloqueante: dependência opcional `fontaine`. | +| `php -l app/Http/Controllers/PortalServicesController.php`; `php -l tests/Feature/PortalServicesTest.php`; Pint dirigido `--test` nesses dois ficheiros | Sintaxe passou e Pint dirigido passou. | +| `git diff --check` | Passou; avisos apenas de normalização CRLF/LF nos docs/forms preexistentes. | ## Prioridade para continuidade @@ -152,75 +152,75 @@ The three investment structure junction tables now support association type, sou Esta adenda prevalece sobre estados e caminhos pesquisados em 2026-10-03, sem apagar o registo historico. A matriz continua sendo o indice consolidado; `simulator-evolution-progress.md` e o diario de execucao, nao uma segunda matriz. -| ID | Requisito | Evidencia actual conferida | Estado actual e accao | Teste/gate | -| --- | --- | --- | --- | --- | -| M-02 | Reconciliar prompts, fontes e pastas indicadas, sem inferir ausencia a partir de um caminho errado. | A pasta `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\prompt` e acessivel; existe conjunto de DOCX, incluindo prompts do simulador e formulários. A pasta `Legislacao` tem codigos e alteracoes fiscais antes registados como nao disponiveis. Foi localizado `Informacao Publica\Legislacao - resumos\Reforma fiscal de 2026.docx`. | Corrigida a afirmacao de indisponibilidade do resumo fiscal. A coleccao foi inventariada e documentos relevantes foram extraidos selectivamente; nao se declara leitura/reconciliacao integral de todos os DOCX. `DUAT.docx` permanece bloqueado por uso do ficheiro e nao foi alterado. | Continuar leitura por dominio e actualizar esta matriz; scans exigem OCR/cotejo visual. | -| D-05 / V-04 | Motor fiscal reproduzivel, por periodo, com fontes, formulas, pressupostos e resultado indeterminado quando faltar dado. | `SimulationTaxRuleExpression` implementa DSL allowlist/DAG/BigRational; `SimulationTaxParameterResolver` trata periodo, `scope_key`, contexto e fonte; endpoint publico projecta parametros; interface apresenta estado/fonte. `RunSimulation` nao invoca o avaliador e continua aritmetica geral/custom. | **Parcial; nenhum motor de apuramento fiscal integrado.** Testes de DSL/resolucao existem, mas sem casos dourados externos ligados a resultados independentes. Nao chamar o fluxo genericamente estimativo de calculo legal. | Executar os testes unitarios tecnicos; gate por componente requer texto legivel/autenticado, regra ligada, snapshot/explicabilidade e resultado de referencia confirmado. | -| D-05 / M-04 | Consolidar CIVA sem somar texto substituido e respeitar vigencia. | O acervo tem Lei 13/2016 (32 paginas, sem camada de texto), PDF denominado rectificado (15 paginas, sem camada de texto), Lei 22/2022 (26 paginas, camada de texto) e alteracao CIVA 2023 (18 paginas, texto extraivel). Ha fontes oficiais INM/Parlamento ja citadas em `official-source-register.md`. | Localizacao confirmada; OCR util nao obtido nesta execucao para scans. Aplicar Lei 13/2016 como base metodologica, reconciliar diplomas anteriores e aplicar posteriores apenas por artigo/vigencia. Anexos rectificados continuam pendentes de cotejo legivel. | Sem activar isencoes/taxas afectadas ate cotejo de fonte/autenticidade e testes antes/depois da vigencia. | -| D-05 / M-04 | Distinguir diploma e resumo secundario, preservar a origem. | BR 248 arquivado tem 21 paginas, 440.018 bytes e SHA-256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`; a extraccao foi conferida nas disposicoes abaixo. A copia do acervo `BR_248_I_SERIE_SUPLEMENTO_2025.pdf` tem 20 paginas e SHA-256 `A0110696253715567AF39C159A21BACFEBC32F944FA43A4EFD18070D36959E50`, logo nao e o mesmo binario. CBF e ISPC_Financas foram localizados, mas sao scans ou extraccoes parciais. | O resumo “Reforma fiscal de 2026” localizado e fonte secundaria. Existencia e texto extraivel nao equivalem a autenticidade/pericial nem a aprovacao do caso. Nao inserir parametros com base em tabelas de resumo. | Comparacao artigo a artigo contra edicao oficial arquivada e teste de vigencia por regra. | -| D-05 / M-09 | Registar revisao profissional informada sem alargar o ambito de documentos. | O proprietario declarou que contabilista/fiscalista reviu e validou o simulador, e ja autorizou formalmente matriz/regras. O PDF OCAM refere-se a Declaracao Anual de Informacao Contabilistica e Fiscal (Modelo 20) da empresa no exercicio de 2025. | Registar a declaracao como informacao/autorizacao do proprietario, nao como validacao independente. O PDF do Modelo 20 nao certifica simulador nem testes fiscais; nao reproduzir PII. A falta de relatorio individual nao impede codigo neutro, testes nem cenarios estimativos fundamentados. | Confirmacao especifica necessaria para regras com fonte/anexo/vigencia ilegivel ou nao consolidada, elegibilidade de beneficios, classificacao pautal/ICE, parametros novos de 2026 e resultados dourados por componente. | -| B-01 | Preservar ambiente e provar apenas comandos efectivamente executados. | Worktree apresenta muitas alteracoes preexistentes/nao comprometidas; foram preservadas. PHP 8.5 permanece requisito do portal. OCR foi tentado com dependencias temporarias; originais nao foram alterados. | Sem reset/clean, migration operacional, activacao fiscal ou mudanca de infraestrutura nesta tranche. | Tests devem usar PHP 8.5.11 e DB descartavel; nao inferir QA a partir da base local. | +| ID | Requisito | Evidencia actual conferida | Estado actual e accao | Teste/gate | +| ----------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| M-02 | Reconciliar prompts, fontes e pastas indicadas, sem inferir ausencia a partir de um caminho errado. | A pasta `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\prompt` e acessivel; existe conjunto de DOCX, incluindo prompts do simulador e formulários. A pasta `Legislacao` tem codigos e alteracoes fiscais antes registados como nao disponiveis. Foi localizado `Informacao Publica\Legislacao - resumos\Reforma fiscal de 2026.docx`. | Corrigida a afirmacao de indisponibilidade do resumo fiscal. A coleccao foi inventariada e documentos relevantes foram extraidos selectivamente; nao se declara leitura/reconciliacao integral de todos os DOCX. `DUAT.docx` permanece bloqueado por uso do ficheiro e nao foi alterado. | Continuar leitura por dominio e actualizar esta matriz; scans exigem OCR/cotejo visual. | +| D-05 / V-04 | Motor fiscal reproduzivel, por periodo, com fontes, formulas, pressupostos e resultado indeterminado quando faltar dado. | `SimulationTaxRuleExpression` implementa DSL allowlist/DAG/BigRational; `SimulationTaxParameterResolver` trata periodo, `scope_key`, contexto e fonte; endpoint publico projecta parametros; interface apresenta estado/fonte. `RunSimulation` nao invoca o avaliador e continua aritmetica geral/custom. | **Parcial; nenhum motor de apuramento fiscal integrado.** Testes de DSL/resolucao existem, mas sem casos dourados externos ligados a resultados independentes. Nao chamar o fluxo genericamente estimativo de calculo legal. | Executar os testes unitarios tecnicos; gate por componente requer texto legivel/autenticado, regra ligada, snapshot/explicabilidade e resultado de referencia confirmado. | +| D-05 / M-04 | Consolidar CIVA sem somar texto substituido e respeitar vigencia. | O acervo tem Lei 13/2016 (32 paginas, sem camada de texto), PDF denominado rectificado (15 paginas, sem camada de texto), Lei 22/2022 (26 paginas, camada de texto) e alteracao CIVA 2023 (18 paginas, texto extraivel). Ha fontes oficiais INM/Parlamento ja citadas em `official-source-register.md`. | Localizacao confirmada; OCR util nao obtido nesta execucao para scans. Aplicar Lei 13/2016 como base metodologica, reconciliar diplomas anteriores e aplicar posteriores apenas por artigo/vigencia. Anexos rectificados continuam pendentes de cotejo legivel. | Sem activar isencoes/taxas afectadas ate cotejo de fonte/autenticidade e testes antes/depois da vigencia. | +| D-05 / M-04 | Distinguir diploma e resumo secundario, preservar a origem. | BR 248 arquivado tem 21 paginas, 440.018 bytes e SHA-256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`; a extraccao foi conferida nas disposicoes abaixo. A copia do acervo `BR_248_I_SERIE_SUPLEMENTO_2025.pdf` tem 20 paginas e SHA-256 `A0110696253715567AF39C159A21BACFEBC32F944FA43A4EFD18070D36959E50`, logo nao e o mesmo binario. CBF e ISPC_Financas foram localizados, mas sao scans ou extraccoes parciais. | O resumo “Reforma fiscal de 2026” localizado e fonte secundaria. Existencia e texto extraivel nao equivalem a autenticidade/pericial nem a aprovacao do caso. Nao inserir parametros com base em tabelas de resumo. | Comparacao artigo a artigo contra edicao oficial arquivada e teste de vigencia por regra. | +| D-05 / M-09 | Registar revisao profissional informada sem alargar o ambito de documentos. | O proprietario declarou que contabilista/fiscalista reviu e validou o simulador, e ja autorizou formalmente matriz/regras. O PDF OCAM refere-se a Declaracao Anual de Informacao Contabilistica e Fiscal (Modelo 20) da empresa no exercicio de 2025. | Registar a declaracao como informacao/autorizacao do proprietario, nao como validacao independente. O PDF do Modelo 20 nao certifica simulador nem testes fiscais; nao reproduzir PII. A falta de relatorio individual nao impede codigo neutro, testes nem cenarios estimativos fundamentados. | Confirmacao especifica necessaria para regras com fonte/anexo/vigencia ilegivel ou nao consolidada, elegibilidade de beneficios, classificacao pautal/ICE, parametros novos de 2026 e resultados dourados por componente. | +| B-01 | Preservar ambiente e provar apenas comandos efectivamente executados. | Worktree apresenta muitas alteracoes preexistentes/nao comprometidas; foram preservadas. PHP 8.5 permanece requisito do portal. OCR foi tentado com dependencias temporarias; originais nao foram alterados. | Sem reset/clean, migration operacional, activacao fiscal ou mudanca de infraestrutura nesta tranche. | Tests devem usar PHP 8.5.11 e DB descartavel; nao inferir QA a partir da base local. | ### Reconciliacao do resumo fiscal secundario localizado — 2026-10-04 Documento lido: `Informacao Publica/Legislacao - resumos/Reforma fiscal de 2026.docx`, SHA-256 `36666904ADB982AD578145E7B70244146C6414299B18F086042A5B4A6C2385B6`. Referencia primaria consultada no BR 248 previamente arquivado e no sumario oficial INM. Uma segunda copia local tem texto extraivel, mas hash e paginação diferem do exemplar arquivado. -| Tema | Alegacao secundária | Dispositivo primário cotejado | Resultado / estado | -| --- | --- | --- | --- | -| IVA vs ISPC | Revogacao dos regimes simplificados de IVA pode ser lida como eliminacao do ISPC. | Lei 9/2025 cria/actualiza ISPC; Lei 10/2025, arts. 3 e 53, altera/revoga disposicoes do CIVA e regula transicao do IVA. | Regimes distintos; divergencia resolvida. ISPC permanece sujeito a elegibilidade propria, nao basta limite de volume. | -| Reembolso IVA | 150 dias e prescricao de 10 anos. | Lei 10/2025, art. 21, n.os 10 e 15 no texto extraido. | Correspondencia textual encontrada. Nao confundir credito/pedido com reconhecimento ou recebimento; regulamento/procedimento e estados de tesouraria continuam por validar. | -| Dedutibilidade | Proporcao em casos de base reduzida e exclusoes. | Lei 10/2025, art. 18(6) e art. 19(2), este referente a operacoes do art. 17-A. | Confirmado apenas no escopo dos dispositivos citados; nao generalizar a todas as operacoes. | -| Operacoes digitais IVA | Responsabilidade do adquirente nao residente/digital descrita de forma ampla. | Lei 10/2025, arts. 6(7), 23(2), 32 e 33. | Depende da natureza da operacao e do sujeito definido em cada artigo; intake nao deve aplicar uma regra universal. | -| Estabelecimento estavel IRPC | Regra de 90 dias pode ser lida como aplicavel tambem a servicos digitais. | Lei 12/2025, art. 3(2)(c), exclui servicos digitais do teste de permanencia por prestacao de servicos; arts. 5(5)–(7) e 62(5) tratam rendimento digital em vias distintas. | Divergencia resolvida; nao importar 90 dias para servico digital por analogia. | -| Mais-valias | Taxa de 32% referida para IRPC. | Lei 12/2025, art. 61-A; Lei 11/2025, art. 54-A do IRPS tem tabela propria. | 32% e regra textual IRPC; nao transportar para IRPS. Aplicacao por categoria e bases ainda requer teste e validacao. | -| Isencoes em importacao e Decreto 52/2025 | Resumo indica caducidade de bens especificos e prazos/condicoes de reembolso especiais. | Lei 10/2025, anexos/dispositivos e Decreto 52/2025 exigem cotejo integral; texto primario legivel correspondente nao foi fechado nesta tranche. | Pendente. Sem regras de isencao nem previsao de reembolso implementadas a partir desta alegacao. | +| Tema | Alegacao secundária | Dispositivo primário cotejado | Resultado / estado | +| ---------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| IVA vs ISPC | Revogacao dos regimes simplificados de IVA pode ser lida como eliminacao do ISPC. | Lei 9/2025 cria/actualiza ISPC; Lei 10/2025, arts. 3 e 53, altera/revoga disposicoes do CIVA e regula transicao do IVA. | Regimes distintos; divergencia resolvida. ISPC permanece sujeito a elegibilidade propria, nao basta limite de volume. | +| Reembolso IVA | 150 dias e prescricao de 10 anos. | Lei 10/2025, art. 21, n.os 10 e 15 no texto extraido. | Correspondencia textual encontrada. Nao confundir credito/pedido com reconhecimento ou recebimento; regulamento/procedimento e estados de tesouraria continuam por validar. | +| Dedutibilidade | Proporcao em casos de base reduzida e exclusoes. | Lei 10/2025, art. 18(6) e art. 19(2), este referente a operacoes do art. 17-A. | Confirmado apenas no escopo dos dispositivos citados; nao generalizar a todas as operacoes. | +| Operacoes digitais IVA | Responsabilidade do adquirente nao residente/digital descrita de forma ampla. | Lei 10/2025, arts. 6(7), 23(2), 32 e 33. | Depende da natureza da operacao e do sujeito definido em cada artigo; intake nao deve aplicar uma regra universal. | +| Estabelecimento estavel IRPC | Regra de 90 dias pode ser lida como aplicavel tambem a servicos digitais. | Lei 12/2025, art. 3(2)(c), exclui servicos digitais do teste de permanencia por prestacao de servicos; arts. 5(5)–(7) e 62(5) tratam rendimento digital em vias distintas. | Divergencia resolvida; nao importar 90 dias para servico digital por analogia. | +| Mais-valias | Taxa de 32% referida para IRPC. | Lei 12/2025, art. 61-A; Lei 11/2025, art. 54-A do IRPS tem tabela propria. | 32% e regra textual IRPC; nao transportar para IRPS. Aplicacao por categoria e bases ainda requer teste e validacao. | +| Isencoes em importacao e Decreto 52/2025 | Resumo indica caducidade de bens especificos e prazos/condicoes de reembolso especiais. | Lei 10/2025, anexos/dispositivos e Decreto 52/2025 exigem cotejo integral; texto primario legivel correspondente nao foi fechado nesta tranche. | Pendente. Sem regras de isencao nem previsao de reembolso implementadas a partir desta alegacao. | ### Inventario de fontes fiscais localizadas nesta revisao -| Ficheiro (fora do repositorio) | Tamanho / SHA-256 | Leitura nesta execucao | -| --- | --- | --- | -| `Legislacao/Lei 13_2016_Reimpressao Codigo do IVA.pdf` | 2.127.554 bytes / `b90dc7c14ece2a2e3784c62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b` | 32 paginas, sem camada de texto; OCR nao produziu texto utilizavel. | -| `Legislacao/RectificaçãoLei.13.2016_Republicação.Codigo.IVA.pdf` | 19.539.751 bytes / `ad675fef757016b730e859c834ab1a1602576260510912dda6f9d320393d415c` | 15 paginas, sem camada de texto; OCR nao produziu texto utilizavel. | -| `Legislacao/alteracao CIVA 2023.pdf` | 708.707 bytes / `0c13f4b03dadea7f2dfe30694650078fa7b9de00ef84cb47389422d23a5271cd` | 18 paginas; texto extraivel, ainda nao reconciliado artigo a artigo. | -| `Legislacao/Legislacao/Legislacao/Lei 22 2022 IVA.pdf` | 1.989.462 bytes / `5bd31ff1231659ce8d89e1260a9de3b067e69e4ace128b3fe0af60e2e928e6af` | 26 paginas; texto extraivel, cotejo oficial por fazer. | -| `Legislacao/Lei 12.2025_IRPC.pdf` | 511.828 bytes / `3b67d10beaa3bf65d62a3d75fa01cc5a8181000d674e7b2ba62d06c60d449757` | 4 paginas; texto extraivel inclui Suplemento 1 do BR 248 e Lei 12/2025. | -| `Legislacao/Legislacao/2009/Lei 4_2009 Código dos Benefícios Fiscais - versão consolidada.pdf` | 253.019 bytes / `61e3439426f1eab4c3eec91f8d4587baa2e71249c7d60bbb17c8d4427dcd0aa3` | 13 paginas; extraccao parcial com aviso de fonte tipografica. | -| `Legislacao/Legislacao/a digitalizar/ISPC_Financas.pdf` | 363.424 bytes / `761502b64d65bfac3f9a6e1b23071a397c1c59a961bd15b27708dc01962ea1de` | 17 paginas; scan/camada de texto insuficiente. | +| Ficheiro (fora do repositorio) | Tamanho / SHA-256 | Leitura nesta execucao | +| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `Legislacao/Lei 13_2016_Reimpressao Codigo do IVA.pdf` | 2.127.554 bytes / `b90dc7c14ece2a2e3784c62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b` | 32 paginas, sem camada de texto; OCR nao produziu texto utilizavel. | +| `Legislacao/RectificaçãoLei.13.2016_Republicação.Codigo.IVA.pdf` | 19.539.751 bytes / `ad675fef757016b730e859c834ab1a1602576260510912dda6f9d320393d415c` | 15 paginas, sem camada de texto; OCR nao produziu texto utilizavel. | +| `Legislacao/alteracao CIVA 2023.pdf` | 708.707 bytes / `0c13f4b03dadea7f2dfe30694650078fa7b9de00ef84cb47389422d23a5271cd` | 18 paginas; texto extraivel, ainda nao reconciliado artigo a artigo. | +| `Legislacao/Legislacao/Legislacao/Lei 22 2022 IVA.pdf` | 1.989.462 bytes / `5bd31ff1231659ce8d89e1260a9de3b067e69e4ace128b3fe0af60e2e928e6af` | 26 paginas; texto extraivel, cotejo oficial por fazer. | +| `Legislacao/Lei 12.2025_IRPC.pdf` | 511.828 bytes / `3b67d10beaa3bf65d62a3d75fa01cc5a8181000d674e7b2ba62d06c60d449757` | 4 paginas; texto extraivel inclui Suplemento 1 do BR 248 e Lei 12/2025. | +| `Legislacao/Legislacao/2009/Lei 4_2009 Código dos Benefícios Fiscais - versão consolidada.pdf` | 253.019 bytes / `61e3439426f1eab4c3eec91f8d4587baa2e71249c7d60bbb17c8d4427dcd0aa3` | 13 paginas; extraccao parcial com aviso de fonte tipografica. | +| `Legislacao/Legislacao/a digitalizar/ISPC_Financas.pdf` | 363.424 bytes / `761502b64d65bfac3f9a6e1b23071a397c1c59a961bd15b27708dc01962ea1de` | 17 paginas; scan/camada de texto insuficiente. | O PDF de compromisso OCAM foi lido apenas para estabelecer o seu escopo (Modelo 20 empresarial, exercicio 2025); nomes, NUIT, codigos e outros dados pessoais foram omitidos. A extraccao dos restantes codigos legiveis e do resumo secundario permanece por reconciliar antes de se afirmar cobertura fiscal completa. ## REC001-REC016 - connected journey tranche (2026-10-04) -| REC | Current evidence and decision | State after this tranche | Next concrete work | -| --- | --- | --- | --- | -| REC001 | `SimulationFormDraft` already encrypts snapshots; `SimulationBusinessPlan` stores a versioned plan snapshot. Adapt existing draft rather than add a second dossier. | Partial: exploratory small-business draft now contains idea and plan stages; not yet one persistent canonical project linked to all modules. | Define canonical project relationship and cross-module ownership before linking records. | -| REC002 | Guided sections already exist in `simulation-form.ts`. | Partial: the small-business exploratory path captures problem, customers, value proposition, sector/activity, location and declared market assumptions in the existing resumable draft. | Add structured market evidence and reusable operational detail. | -| REC003 | Business plan and cashflow tools have independent tests and versioned snapshots. | Partial: financial plan is available in exploratory small-business path; fiscal rules remain separate and may be indeterminate. | Test full plan-to-simulator reconciliation and scenario history. | -| REC004 | Existing plan supports user-edited financial assumptions. | Partial: a free-text milestone field accepts planned/actual notes and dates; it is not yet a structured plan-vs-actual register. | Add structured milestones, status transitions and cost reconciliation after canonical dossier ownership is settled. | -| REC005 | Public procedures and DUAT guidance exist. | Partial: the location stage links to `/terra-duat` and `/como-investir`; no project-specific checklist is saved. | Add stage-specific checklist with source and applicability, without universal required documents. | -| REC006 | Interest/CRM intake exists; SuiteCRM remains disabled. | Partial: internal intake is not official submission. | Record referrals and proof only when an actual supported channel exists. | -| REC007 | Indicator schemas/data exist but source metadata/catalogue coverage is partial. | Partial. | Expose a small catalog with unit, period, geography, source and coverage metadata. | -| REC008 | Territorial catalogue exists; completeness and comparative analytics are not established. | Partial. | Compare only verified coverage; preserve list view where coordinates are absent. | -| REC009 | Several CSV exports exist; no common dossier export established. | Partial. | Export the project and its versioned plan once a canonical aggregate is defined. | -| REC010 | Existing assistance/AI paths have controls; explanations and scope vary. | Partial. | Use rule-based, cited suggestions first; keep AI optional and reviewable. | -| REC011 | Portal operations include status and audit data, but no complete journey funnel verified. | Partial. | Define cohort, denominator, declared vs verified outcomes and privacy rules. | -| REC012 | Automated tests exist; no full connected pilot/browser/accessibility pass executed in this tranche. | Partial. | Test end-to-end journey, authorization, keyboard, narrow viewport and regressions. | -| REC013 | Education tables/controllers exist. | Partial: the draft captures user-declared competency/team needs as text; it does not structure availability, gap, solution, cost and deadline or match a training provider. | Map available occupation/skill entities and build declared needs/actions without implying certification. | -| REC014 | Education discovery and partner routes exist; no verified project-to-provider match. | Partial. | Match by explicit task, location, availability, cost and evidence. | -| REC015 | Portal aggregates exist in some dashboards; safe public aggregate semantics need review. | Partial. | Document universe, method, periods and suppression/privacy controls. | -| REC016 | Finance catalog/search exists but no complete product-level compatibility contract verified. | Partial: the draft accepts a financing/resource need and optional amount, but does not match products or establish pre-eligibility. | Show compatible, incompatible by documented criterion, or insufficient information; no approval promise. | +| REC | Current evidence and decision | State after this tranche | Next concrete work | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| REC001 | `SimulationFormDraft` already encrypts snapshots; `SimulationBusinessPlan` stores a versioned plan snapshot. Adapt existing draft rather than add a second dossier. | Partial: exploratory small-business draft now contains idea and plan stages; not yet one persistent canonical project linked to all modules. | Define canonical project relationship and cross-module ownership before linking records. | +| REC002 | Guided sections already exist in `simulation-form.ts`. | Partial: the small-business exploratory path captures problem, customers, value proposition, sector/activity, location and declared market assumptions in the existing resumable draft. | Add structured market evidence and reusable operational detail. | +| REC003 | Business plan and cashflow tools have independent tests and versioned snapshots. | Partial: financial plan is available in exploratory small-business path; fiscal rules remain separate and may be indeterminate. | Test full plan-to-simulator reconciliation and scenario history. | +| REC004 | Existing plan supports user-edited financial assumptions. | Partial: a free-text milestone field accepts planned/actual notes and dates; it is not yet a structured plan-vs-actual register. | Add structured milestones, status transitions and cost reconciliation after canonical dossier ownership is settled. | +| REC005 | Public procedures and DUAT guidance exist. | Partial: the location stage links to `/terra-duat` and `/como-investir`; no project-specific checklist is saved. | Add stage-specific checklist with source and applicability, without universal required documents. | +| REC006 | Interest/CRM intake exists; SuiteCRM remains disabled. | Partial: internal intake is not official submission. | Record referrals and proof only when an actual supported channel exists. | +| REC007 | Indicator schemas/data exist but source metadata/catalogue coverage is partial. | Partial. | Expose a small catalog with unit, period, geography, source and coverage metadata. | +| REC008 | Territorial catalogue exists; completeness and comparative analytics are not established. | Partial. | Compare only verified coverage; preserve list view where coordinates are absent. | +| REC009 | Several CSV exports exist; no common dossier export established. | Partial. | Export the project and its versioned plan once a canonical aggregate is defined. | +| REC010 | Existing assistance/AI paths have controls; explanations and scope vary. | Partial. | Use rule-based, cited suggestions first; keep AI optional and reviewable. | +| REC011 | Portal operations include status and audit data, but no complete journey funnel verified. | Partial. | Define cohort, denominator, declared vs verified outcomes and privacy rules. | +| REC012 | Automated tests exist; no full connected pilot/browser/accessibility pass executed in this tranche. | Partial. | Test end-to-end journey, authorization, keyboard, narrow viewport and regressions. | +| REC013 | Education tables/controllers exist. | Partial: the draft captures user-declared competency/team needs as text; it does not structure availability, gap, solution, cost and deadline or match a training provider. | Map available occupation/skill entities and build declared needs/actions without implying certification. | +| REC014 | Education discovery and partner routes exist; no verified project-to-provider match. | Partial. | Match by explicit task, location, availability, cost and evidence. | +| REC015 | Portal aggregates exist in some dashboards; safe public aggregate semantics need review. | Partial. | Document universe, method, periods and suppression/privacy controls. | +| REC016 | Finance catalog/search exists but no complete product-level compatibility contract verified. | Partial: the draft accepts a financing/resource need and optional amount, but does not match products or establish pre-eligibility. | Show compatible, incompatible by documented criterion, or insufficient information; no approval promise. | Pilot: synthetic, unseeded small-business idea using the public `/Simulador` path, `journey_type=small_business` and `purpose=simulate_only`. It captures optional idea/market responses, existing sector and territorial selections, competency/resource/milestone notes and the versioned financial plan in the existing draft. It can save locally and, when signed in, use the existing server draft. Market data, competency matches and financing products are not verified or matched. The pilot is not a public business listing, formal filing, fiscal determination, financing pre-approval or official submission. No operational fixture was created. ### REC001–REC016 follow-up — 2026-10-04 -| Area | Delivered in this tranche | Remaining gate | -| --- | --- | --- | -| Dossier, idea and finance | Reused encrypted `SimulationFormDraft` and existing versioned business-plan snapshot; added dossier CSV with market claim/source, location, finance assumptions, declared needs, team and execution rows. | Cross-module canonical project identity and plan snapshot reconciliation remain future work. | -| Team and execution | New structured planner rows capture competency, availability/gap, planned response, estimated cost/date, milestone status and planned/actual amounts. Server enforces JSON shape, state whitelist, 100-row limit and nonnegative amounts. | Matching to education providers/certified skills and reconciling realized amounts to accounting/plan remain pending. | -| Market origin and indicators | UI and dossier explicitly say “Informação declarada, não verificada”; source/date optional. Exports distinguish declared assumptions and estimates from selected territorial catalogue values. | Official indicator catalogue with source, period, geography, unit and coverage is not present as a complete connected dataset. | -| Financing | Missing product catalogue and criteria produce “Informação insuficiente”; no pre-eligibility result is invented. | Documented product feed, current sources/eligibility criteria and tests required before indicative matching. | -| Computer/mobile journeys | Targeted frontend/API/PHP tests pass. | Real Edge/browser checks at desktop/mobile widths, idea/existing-business/incomplete-data save-recovery and validation are not completed because runner policy rejected launching `msedge.exe`. Browser gate stays open. | +| Area | Delivered in this tranche | Remaining gate | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Dossier, idea and finance | Reused encrypted `SimulationFormDraft` and existing versioned business-plan snapshot; added dossier CSV with market claim/source, location, finance assumptions, declared needs, team and execution rows. | Cross-module canonical project identity and plan snapshot reconciliation remain future work. | +| Team and execution | New structured planner rows capture competency, availability/gap, planned response, estimated cost/date, milestone status and planned/actual amounts. Server enforces JSON shape, state whitelist, 100-row limit and nonnegative amounts. | Matching to education providers/certified skills and reconciling realized amounts to accounting/plan remain pending. | +| Market origin and indicators | UI and dossier explicitly say “Informação declarada, não verificada”; source/date optional. Exports distinguish declared assumptions and estimates from selected territorial catalogue values. | Official indicator catalogue with source, period, geography, unit and coverage is not present as a complete connected dataset. | +| Financing | Missing product catalogue and criteria produce “Informação insuficiente”; no pre-eligibility result is invented. | Documented product feed, current sources/eligibility criteria and tests required before indicative matching. | +| Computer/mobile journeys | Targeted frontend/API/PHP tests pass. | Real Edge/browser checks at desktop/mobile widths, idea/existing-business/incomplete-data save-recovery and validation are not completed because runner policy rejected launching `msedge.exe`. Browser gate stays open. | Evidence: frontend suite 128/128; isolated PostgreSQL 17/PHP 8.5.11 full PHP suite 255/255 (2,601 assertions); TypeScript check, production build (2,399 modules), PHPStan (0 errors), PHP request lint and `git diff --check` passed. Build reports only the optional `fontaine` fallback warning. No operational fixture or database was used; the temporary QA database was isolated on port 55441. @@ -237,34 +237,34 @@ Esta secção é a reconciliação corrente desta matriz para Documentos e Formu ### Documentos — quatro recursos, revisão separada por dimensão -| ID recurso / ID documento | Título e URL de gestão | Origem efectivamente identificada | Autenticidade | Vigência/actualidade | Direitos de divulgação / autorização | Bloqueio e proposta individual | -|---|---|---|---|---|---|---| -| `1` / `5c860c48-e8cb-4d8d-ab9b-2e3ea7036ce2` | Lei n.º 22/2022, alteração ao CIVA (republicação). `http://localhost:8000/management/portal-services` — localizar recurso 1. | [INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da); catálogo/WebDAV oficial AT; tamanho e SHA-256 coincidem com a cópia oficial registada. | Confirmada para a edição e bytes. | Marcada `amended`; art. 17(1) foi localizado, mas não é consolidação integral actual. Não bloqueia publicação como documento histórico se assim rotulado; bloqueia tratá-la como texto actual consolidado. | Documento e recurso `rights_status=unconfirmed`, classificação `internal`; autorização para redistribuir esta cópia PDF/fac-símile não documentada. | **Não pronta.** Gestor regista base de reutilização/divulgação, depois classifica explicitamente como pública e publica. Proposta: Lei 22/2022 histórica, com aviso de alterações posteriores, nunca como CIVA consolidado. Fonte e malware passam; direitos bloqueiam publicação. | -| `2` / `64c46765-96b9-4f43-afc6-f5b730c010c4` | BR n.º 248/2025, Suplemento n.º 1. `http://localhost:8000/management/portal-services` — localizar recurso 2. | [Ficha INM](https://inm.gov.mz/pt-br/content/suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie); WebDAV oficial AT; 440.018 bytes e SHA-256 coincidem com a edição oficial registada. | Confirmada para a edição e bytes. | Contém seis diplomas distintos (Leis 7–12/2025); situação `unknown`. Citações/artigos localizados no PDF, mas não há uma vigência única para o boletim. | Direitos do ficheiro composto `unconfirmed`, classificação `internal`; autorização de redistribuição integral não registada. | **Não pronta.** Gestor documenta direitos do boletim composto. Proposta: publicar como edição oficial do BR com aviso de que os seis diplomas não são uma regra única ou consolidação. Não activar cálculos pela associação temática. | -| `3` / `58abf343-e1f5-446d-a9cb-3255f376ece9` | BR n.º 89/2017, I Série — aviso de rectificação da Lei 13/2016. `http://localhost:8000/management/portal-services` — localizar recurso 3. | [Ficha INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569) identifica o aviso na p. 569. A página respondeu HTTP 200, mas não expõe ligação para PDF integral; só a ficha geral do BR. | **Binário local não autenticado:** hash local conhecido, sem comparação a exemplar oficial. Texto local refere alteração do art. 6(5), eliminação do art. 14(2)(c) e rectificação dos anexos I–III do art. 9; não autentica o PDF. | Efeito temporal e anexos rectificados não foram cotejados com cópia autêntica; `legal_status=unknown`. | Direitos `unconfirmed`, classificação `internal`. | **Não pronta.** Necessária cópia oficial dos bytes e anexos, comparação/hash e decisão sobre divulgação do fac-símile. A busca confirmou a ficha, não encontrou o binário. Depois, gestor decide direitos/publicação. | -| `4` / `b0ea969a-30ec-477b-8b8b-b7af82a11bfe` | Codificador DPA, INE, edição 07-06-2012. `http://localhost:8000/management/portal-services` — localizar recurso 4. | [PDF oficial INE](https://www.ine.gov.mz/documents/20119/44337/Codificador%20da%20Divisao%20Politico%20administrativa.pdf/14aed3d0-9296-c038-dbac-041a5cf40f1d); respondeu HTTP 200 e `application/pdf`; hash local coincide com o PDF oficial previamente descarregado. | Confirmada para a edição e bytes. | Edição de 2012; actualidade da nomenclatura/códigos requer cotejo com o mestre territorial vigente. | Direitos do artefacto PDF `unconfirmed`, classificação `internal`. | **Não pronta.** Gestor documenta direitos. Proposta: referência histórica datada; manter aviso da edição 2012 e não usá-la como prova automática da actualidade territorial. | +| ID recurso / ID documento | Título e URL de gestão | Origem efectivamente identificada | Autenticidade | Vigência/actualidade | Direitos de divulgação / autorização | Bloqueio e proposta individual | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `1` / `5c860c48-e8cb-4d8d-ab9b-2e3ea7036ce2` | Lei n.º 22/2022, alteração ao CIVA (republicação). `http://localhost:8000/management/portal-services` — localizar recurso 1. | [INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da); catálogo/WebDAV oficial AT; tamanho e SHA-256 coincidem com a cópia oficial registada. | Confirmada para a edição e bytes. | Marcada `amended`; art. 17(1) foi localizado, mas não é consolidação integral actual. Não bloqueia publicação como documento histórico se assim rotulado; bloqueia tratá-la como texto actual consolidado. | Documento e recurso `rights_status=unconfirmed`, classificação `internal`; autorização para redistribuir esta cópia PDF/fac-símile não documentada. | **Não pronta.** Gestor regista base de reutilização/divulgação, depois classifica explicitamente como pública e publica. Proposta: Lei 22/2022 histórica, com aviso de alterações posteriores, nunca como CIVA consolidado. Fonte e malware passam; direitos bloqueiam publicação. | +| `2` / `64c46765-96b9-4f43-afc6-f5b730c010c4` | BR n.º 248/2025, Suplemento n.º 1. `http://localhost:8000/management/portal-services` — localizar recurso 2. | [Ficha INM](https://inm.gov.mz/pt-br/content/suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie); WebDAV oficial AT; 440.018 bytes e SHA-256 coincidem com a edição oficial registada. | Confirmada para a edição e bytes. | Contém seis diplomas distintos (Leis 7–12/2025); situação `unknown`. Citações/artigos localizados no PDF, mas não há uma vigência única para o boletim. | Direitos do ficheiro composto `unconfirmed`, classificação `internal`; autorização de redistribuição integral não registada. | **Não pronta.** Gestor documenta direitos do boletim composto. Proposta: publicar como edição oficial do BR com aviso de que os seis diplomas não são uma regra única ou consolidação. Não activar cálculos pela associação temática. | +| `3` / `58abf343-e1f5-446d-a9cb-3255f376ece9` | BR n.º 89/2017, I Série — aviso de rectificação da Lei 13/2016. `http://localhost:8000/management/portal-services` — localizar recurso 3. | [Ficha INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569) identifica o aviso na p. 569. A página respondeu HTTP 200, mas não expõe ligação para PDF integral; só a ficha geral do BR. | **Binário local não autenticado:** hash local conhecido, sem comparação a exemplar oficial. Texto local refere alteração do art. 6(5), eliminação do art. 14(2)(c) e rectificação dos anexos I–III do art. 9; não autentica o PDF. | Efeito temporal e anexos rectificados não foram cotejados com cópia autêntica; `legal_status=unknown`. | Direitos `unconfirmed`, classificação `internal`. | **Não pronta.** Necessária cópia oficial dos bytes e anexos, comparação/hash e decisão sobre divulgação do fac-símile. A busca confirmou a ficha, não encontrou o binário. Depois, gestor decide direitos/publicação. | +| `4` / `b0ea969a-30ec-477b-8b8b-b7af82a11bfe` | Codificador DPA, INE, edição 07-06-2012. `http://localhost:8000/management/portal-services` — localizar recurso 4. | [PDF oficial INE](https://www.ine.gov.mz/documents/20119/44337/Codificador%20da%20Divisao%20Politico%20administrativa.pdf/14aed3d0-9296-c038-dbac-041a5cf40f1d); respondeu HTTP 200 e `application/pdf`; hash local coincide com o PDF oficial previamente descarregado. | Confirmada para a edição e bytes. | Edição de 2012; actualidade da nomenclatura/códigos requer cotejo com o mestre territorial vigente. | Direitos do artefacto PDF `unconfirmed`, classificação `internal`. | **Não pronta.** Gestor documenta direitos. Proposta: referência histórica datada; manter aviso da edição 2012 e não usá-la como prova automática da actualidade territorial. | Estado técnico comum dos quatro: ficheiros `active`, ClamAV 1.5.4 `clean`, hash analisado igual ao guardado, nenhum publicado. A página de gestão não é uma rota individual: `/management/portal-services/resources/{id}` não é GET e não deve ser usada como ligação de detalhe. ### Negócios existentes — dois rascunhos privados -| ID | Título armazenado / modalidade | URL de gestão e de proprietário | Origem, conteúdo conhecido e faltas | Bloqueio/regra; decisão proposta | -|---|---|---|---|---| -| `7208ef2b-0693-49ba-b0e2-9e52938b1a99` | Título privado guardado: “privado”; modalidade `mining_license`; `public_title=NULL`; estado `draft`; classificação `confidential`. | Gestão: `http://localhost:8000/management/negocios` (fila; rascunho não aparece). Não há GET administrativo individual. Ficha privada do proprietário: `http://localhost:8000/investidor/negocios/7208ef2b-0693-49ba-b0e2-9e52938b1a99`. | `source_system` e `legacy_id` nulos; payload só tem título e versão/etapa do assistente. Não há fonte, requerente/proponente, descrição de licença, localização, entidade emissora, referência/licença, actividade, validade ou autorização de divulgação. | Não é publicável como oportunidade: o registo é rascunho confidencial e não há conteúdo suficiente para descrever uma licença ou provar a autorização do proponente. Não há bloqueio por falta de operador de uma zona; trata-se de outra categoria. **Decisão do proprietário necessária:** identificar o que “privado” designa e fornecer fonte/autorização antes de submeter. Nenhuma publicação proposta agora. | -| `f65c1d9d-5820-4030-8dc1-fe7ff3de0cd2` | Título privado guardado: “venda de empresa”; modalidade `company_sale`; `public_title=NULL`; estado `draft`; classificação `confidential`. | Gestão: `http://localhost:8000/management/negocios` (fila; rascunho não aparece). Não há GET administrativo individual. Ficha privada do proprietário: `http://localhost:8000/investidor/negocios/f65c1d9d-5820-4030-8dc1-fe7ff3de0cd2`. | `source_system` e `legacy_id` nulos; payload só contém título e versão/etapa. Sem empresa identificada, descrição, proponente, mandato quando aplicável, origem da informação, localização, modalidade da transacção, valor/moeda e autorização específica para divulgar. | O estado privado e o fluxo de revisão são controlos existentes. **Decisão do proprietário necessária:** completar a ficha e declarar a autoridade para divulgar. Só depois da submissão entram na fila e nas etapas de revisão. Não publicar agora. | +| ID | Título armazenado / modalidade | URL de gestão e de proprietário | Origem, conteúdo conhecido e faltas | Bloqueio/regra; decisão proposta | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `7208ef2b-0693-49ba-b0e2-9e52938b1a99` | Título privado guardado: “privado”; modalidade `mining_license`; `public_title=NULL`; estado `draft`; classificação `confidential`. | Gestão: `http://localhost:8000/management/negocios` (fila; rascunho não aparece). Não há GET administrativo individual. Ficha privada do proprietário: `http://localhost:8000/investidor/negocios/7208ef2b-0693-49ba-b0e2-9e52938b1a99`. | `source_system` e `legacy_id` nulos; payload só tem título e versão/etapa do assistente. Não há fonte, requerente/proponente, descrição de licença, localização, entidade emissora, referência/licença, actividade, validade ou autorização de divulgação. | Não é publicável como oportunidade: o registo é rascunho confidencial e não há conteúdo suficiente para descrever uma licença ou provar a autorização do proponente. Não há bloqueio por falta de operador de uma zona; trata-se de outra categoria. **Decisão do proprietário necessária:** identificar o que “privado” designa e fornecer fonte/autorização antes de submeter. Nenhuma publicação proposta agora. | +| `f65c1d9d-5820-4030-8dc1-fe7ff3de0cd2` | Título privado guardado: “venda de empresa”; modalidade `company_sale`; `public_title=NULL`; estado `draft`; classificação `confidential`. | Gestão: `http://localhost:8000/management/negocios` (fila; rascunho não aparece). Não há GET administrativo individual. Ficha privada do proprietário: `http://localhost:8000/investidor/negocios/f65c1d9d-5820-4030-8dc1-fe7ff3de0cd2`. | `source_system` e `legacy_id` nulos; payload só contém título e versão/etapa. Sem empresa identificada, descrição, proponente, mandato quando aplicável, origem da informação, localização, modalidade da transacção, valor/moeda e autorização específica para divulgar. | O estado privado e o fluxo de revisão são controlos existentes. **Decisão do proprietário necessária:** completar a ficha e declarar a autoridade para divulgar. Só depois da submissão entram na fila e nas etapas de revisão. Não publicar agora. | ### Oportunidades já públicas — seis registos; origem de demonstração identificada Todos os seis registos correspondem exactamente ao conjunto fixo de `database/seeders/OpportunitySeeder.php`: títulos, referências, províncias, valores e impactos coincidem; todos usam `promoter_name="Promotor em validação"`, a mesma data de criação/publicação `2026-09-24 19:02:25`, `last_validated_at=NULL` e `geography_scope=unspecified`. A tabela `opportunities` não guarda `source_system`, actor de importação, documento de origem nem autorização do proponente. Assim, **a origem do conteúdo é um conjunto de demonstração definido no seeder; não é possível provar pela base qual processo executou/importou cada linha**. Não foram removidos nem alterados. -| ID / referência | Título | URL de gestão existente | Informação presente; falta individual | Estado e acção proposta | -|---|---|---|---|---| -| `1` / `INV-MZ-AGR-001` | Unidade de processamento agrícola em Nampula | `http://localhost:8000/management/opportunities/1/context` (edita só contexto; não é gestão editorial/publicação da oportunidade) | Sector agro-negócio, Nampula, USD 2.500.000, impacto textual e maturidade 4; falta fonte comercial, promotor/autorização, data de validação e localização abaixo da província. | Já pública; não há proposta de republicação. Decisão humana: confirmar que deve permanecer como exemplo visível ou entregar evidência de uma proposta real autorizada. | -| `2` / `INV-MZ-ENE-002` | Mini-rede solar para polo produtivo | `http://localhost:8000/management/opportunities/2/context` (só contexto) | Sector energia, Zambézia, USD 1.200.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual: demonstrar como exemplo, ou fornecer origem real autorizada. | -| `3` / `INV-MZ-TUR-003` | Eco-lodge costeiro com integração comunitária | `http://localhost:8000/management/opportunities/3/context` (só contexto) | Turismo, Inhambane, USD 1.800.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual. | -| `4` / `INV-MZ-INF-004` | Centro logístico provincial | `http://localhost:8000/management/opportunities/4/context` (só contexto) | Infraestruturas, Sofala, USD 8.500.000 e impacto textual; faltam fonte, proponente/consentimento, validação e distrito/localização exacta. | Já pública; mesma decisão individual. | -| `5` / `INV-MZ-BLUE-005` | Processamento de pescado e cadeia fria | `http://localhost:8000/management/opportunities/5/context` (só contexto) | Economia azul, Zambézia, USD 900.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual. | -| `6` / `INV-MZ-FIN-006` | Plataforma de crédito para PME agrícolas | `http://localhost:8000/management/opportunities/6/context` (só contexto) | Mercado financeiro, Maputo Cidade, USD 3.000.000 e impacto textual; faltam fonte, entidade promotora/autorização, validação e localização. O título não prova autorização para conceder crédito/captar depósitos. | Já pública; mesma decisão individual. Não deve ser apresentada como operador autorizado de crédito sem prova própria. | +| ID / referência | Título | URL de gestão existente | Informação presente; falta individual | Estado e acção proposta | +| ----------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `1` / `INV-MZ-AGR-001` | Unidade de processamento agrícola em Nampula | `http://localhost:8000/management/opportunities/1/context` (edita só contexto; não é gestão editorial/publicação da oportunidade) | Sector agro-negócio, Nampula, USD 2.500.000, impacto textual e maturidade 4; falta fonte comercial, promotor/autorização, data de validação e localização abaixo da província. | Já pública; não há proposta de republicação. Decisão humana: confirmar que deve permanecer como exemplo visível ou entregar evidência de uma proposta real autorizada. | +| `2` / `INV-MZ-ENE-002` | Mini-rede solar para polo produtivo | `http://localhost:8000/management/opportunities/2/context` (só contexto) | Sector energia, Zambézia, USD 1.200.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual: demonstrar como exemplo, ou fornecer origem real autorizada. | +| `3` / `INV-MZ-TUR-003` | Eco-lodge costeiro com integração comunitária | `http://localhost:8000/management/opportunities/3/context` (só contexto) | Turismo, Inhambane, USD 1.800.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual. | +| `4` / `INV-MZ-INF-004` | Centro logístico provincial | `http://localhost:8000/management/opportunities/4/context` (só contexto) | Infraestruturas, Sofala, USD 8.500.000 e impacto textual; faltam fonte, proponente/consentimento, validação e distrito/localização exacta. | Já pública; mesma decisão individual. | +| `5` / `INV-MZ-BLUE-005` | Processamento de pescado e cadeia fria | `http://localhost:8000/management/opportunities/5/context` (só contexto) | Economia azul, Zambézia, USD 900.000 e impacto textual; faltam fonte, proponente/consentimento, validação e localização exacta. | Já pública; mesma decisão individual. | +| `6` / `INV-MZ-FIN-006` | Plataforma de crédito para PME agrícolas | `http://localhost:8000/management/opportunities/6/context` (só contexto) | Mercado financeiro, Maputo Cidade, USD 3.000.000 e impacto textual; faltam fonte, entidade promotora/autorização, validação e localização. O título não prova autorização para conceder crédito/captar depósitos. | Já pública; mesma decisão individual. Não deve ser apresentada como operador autorizado de crédito sem prova própria. | O status `published` já está activo, mas “Promotor em validação” e a data de publicação não são verificação. A confirmação do responsável deve ser **por finalidade**: manter como conteúdo demonstrativo claramente identificado, ou substituir por evidência e autorização do proponente de cada oportunidade. Nenhuma acção em massa foi feita. @@ -272,24 +272,24 @@ O status `published` já está activo, mas “Promotor em validação” e a dat Situação comum observada: `publication_status=draft`, `validation_status=validated`, `reviewer_id=NULL`. Existem 19 relações territoriais adicionais, todas `pending`. As localizações primárias abaixo correspondem a registos públicos do mestre territorial, mas a localização jurídica adicional não entra em pesquisa pública enquanto a relação estiver pendente. Para ZEE/ZFI, seis referências legais existentes estão `pending` e três registos não têm referência legal estruturada. Para parques, existe fonte HTTPS em `source_evidence`; a fonte pode ser promocional/institucional e os claims têm de ficar limitados ao que a página sustenta. O código permite a revisão administrativa por gestor diferente do autor; não exige operador nem certificador externo. -| ID / código | Título, localização e URL de gestão | Origem e alcance efectivamente registado | Falta exacta; bloqueio; proposta individual | -|---|---|---|---| -| `1b57649a-f522-4cd5-8e97-32e82b6e1b37` / `MOZ-BELULUANE-PARK` | Parque Industrial de Beluluane — Matola Rio, Boane. `http://localhost:8000/management/investment/zones/1b57649a-f522-4cd5-8e97-32e82b6e1b37` | [APIEX — Beluluane](https://apiex.gov.mz/beluluane-industrial-park-free-zone/); referência institucional, não comprova regime, incentivos ou disponibilidade. Reconciliação local distingue complexo ~780 ha, parque 600 ha, Mozal 180 ha e 55 ha de ZFI; [MozParks](https://mozparks.co.mz/) também reporta área para arrendamento com âmbito diferente. | Relação Matola Rio pendente; perímetros e relação das áreas não reconciliados; não há confirmação de lotes disponíveis. Sem revisor. **Proposta:** pode ser considerado para publicação informativa depois de o gestor confirmar existência/escopo e publicar sem declarar área disponível; manter sem operador se desconhecido. | -| `d25d4eb1-744a-42e1-943a-47d33019c31c` / `MOZ-BELULUANE-ZFI` | Zona Franca Industrial de Beluluane — Matola Rio, Boane. `http://localhost:8000/management/investment/zones/d25d4eb1-744a-42e1-943a-47d33019c31c` | [APIEX — Beluluane](https://apiex.gov.mz/beluluane-industrial-park-free-zone/); referência à Resolução Interna do Conselho de Ministros n.º 15/99, 12-10-1999. | Artigo/efeito legal actual e fonte oficial primária ainda pendentes; `investment_legal_sources.validation_status=pending`; relação territorial pendente; sem revisor. **Não pronta.** Revisor confirma texto e vigência, valida território e só então gestor decide publicação. | -| `0d216b6d-598c-4672-be2a-294e5e924da5` / `MOZ-CDP-ANCUABE` | Parque Industrial de Ancuabe — distrito Ancuabe, Cabo Delgado. `http://localhost:8000/management/investment/zones/0d216b6d-598c-4672-be2a-294e5e924da5` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); suporta associação distrital na iniciativa provincial, não instalação operacional independente. | Fonte de promotor, não primária oficial; relação Ancuabe pendente; estado planeado, sem área/operação/disponibilidade confirmadas; sem revisor. **Não pronta** segundo o requisito de fonte oficial. Decisão: obter fonte oficial adequada ou manter rascunho; se publicar no futuro, rotular “planeado/iniciativa”, não parque operacional. | -| `ed4d2fba-95d5-42e3-8ca7-1f520de1036a` / `MOZ-CDP-BALAMA` | Parque Industrial de Balama — distrito Balama, Cabo Delgado. `http://localhost:8000/management/investment/zones/ed4d2fba-95d5-42e3-8ca7-1f520de1036a` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); sustenta associação à iniciativa, não operação individual. | Fonte oficial individual em falta; relação Balama pendente; sem área, operação ou disponibilidade; sem revisor. **Não pronta**; decisão individual igual à de Ancuabe, sem publicação em massa. | -| `aafbbac1-e8d8-4f33-8f93-d0acedc1ff95` / `MOZ-CDP-MONTEPUEZ` | Parque Industrial de Montepuez — distrito Montepuez, Cabo Delgado. `http://localhost:8000/management/investment/zones/aafbbac1-e8d8-4f33-8f93-d0acedc1ff95` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); associação distrital, não operação individual. | Fonte oficial individual em falta; relação Montepuez pendente; sem área/operação/disponibilidade; sem revisor. **Não pronta**; obter evidência específica ou manter rascunho. | -| `4b002994-b11c-4adf-80f3-12966bf669cb` / `MOZ-CDP-PALMA` | Parque Industrial de Palma — distrito Palma, Cabo Delgado. `http://localhost:8000/management/investment/zones/4b002994-b11c-4adf-80f3-12966bf669cb` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); associação distrital, não operação individual. | Fonte oficial individual em falta; relação Palma pendente; sem área/operação/disponibilidade; sem revisor. **Não pronta**; obter evidência específica ou manter rascunho. | -| `59fe5844-44de-4fd6-8968-9d23f80a77bb` / `MOZ-CRUSSE-JAMALI-ZEE` | Zona de Estância de Turismo Integrado de Crusse e Jamali — Matibane, Mossuril. `http://localhost:8000/management/investment/zones/59fe5844-44de-4fd6-8968-9d23f80a77bb` | [INM — BR 70/I Série, p. 571](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-70-de-300813-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-571); Decreto n.º 47/2013, 30-08-2013. A página respondeu HTTP 200. | Artigo e efeito legal marcados pendentes no registo; fonte jurídica pendente; relação de âmbito jurídico Matibane pendente; sem revisor. **Não pronta.** Revisor confere decreto/âmbito e vigência; a ausência de operador não é bloqueio. | -| `30f38ba2-3a55-4cb8-8c7b-97a0445c5614` / `MOZ-MANGA-MUNGASSA-ZEE` | Zona Económica Especial de Manga-Mungassa — Cidade da Beira, Sofala. `http://localhost:8000/management/investment/zones/30f38ba2-3a55-4cb8-8c7b-97a0445c5614` | [APIEX — Manga-Mungassa](https://apiex.gov.mz/zee/manga-mungassa-special-economic-zone/) e Decreto n.º 22/2012, 06-06-2012. | Artigo/efeito legal pendente; URL APIEX não respondeu nesta sessão; fonte legal permanece pending; relação territorial pendente; sem revisor. **Não pronta.** Revisor confirma o acto em fonte primária acessível; publicação não depende de operador identificado. | -| `e67a94bc-7d75-4c03-8b66-0bbe7ad034bf` / `MOZ-MINHEWENE-ZFI` | Zona Franca Industrial de Minhewene — Nampula (apenas província). `http://localhost:8000/management/investment/zones/e67a94bc-7d75-4c03-8b66-0bbe7ad034bf` | [APIEX — Minhewene](https://apiex.gov.mz/zona-franca-industrial-de-minhewene/), declara identidade, província e área e aponta Decreto n.º 51/2011; a fonte foi inacessível nesta verificação. | Não existe `legal_source_id` para o diploma; localização mais precisa que província por confirmar; relação territorial pendente; sem revisor. **Não pronta.** Criar/validar referência ao Decreto 51/2011 e confirmar território antes de publicar como ZFI. | -| `74c5dab4-afa7-4f36-8f75-7df0a900b4fe` / `MOZ-MOCUBA-ZEE` | Zona Económica Especial de Mocuba — distrito Mocuba, Zambézia. `http://localhost:8000/management/investment/zones/74c5dab4-afa7-4f36-8f75-7df0a900b4fe` | [INM — BR 46/I Série, p. 1259](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-46-de-060614-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1259), Decreto n.º 28/2014. A página respondeu HTTP 200. | Artigo/efeito legal por confirmar; relações Mocuba e Munhamade (duas localizações distintas) pendentes; fonte jurídica pendente; sem revisor. **Não pronta.** Rever as duas localizações contra o diploma separadamente; não fundir com a ZFI de Mocuba. | -| `530e738e-8b9a-429c-b00e-e5135394acd4` / `MOZ-MOCUBA-ZFI` | Zona Franca Industrial de Mocuba — Cidade de Mocuba. `http://localhost:8000/management/investment/zones/530e738e-8b9a-429c-b00e-e5135394acd4` | [APIEX — Mocuba](https://apiex.gov.mz/zif/mocuba-free-industrial-zone/); Decreto n.º 29/2014, 06-06-2014. | Fonte APIEX inacessível nesta sessão; diploma/artigo e vigência pendentes; relação territorial pendente; fonte legal pendente; sem revisor. **Não pronta.** Rever diploma separado da ZEE, confirmar território e só depois publicar. | -| `dc76a5f7-b1ba-4c5d-a9e3-66d1046be78d` / `MOZ-MUNHAVA-PARK` | Parque Industrial e Logístico de Munhava — Cidade da Beira, Sofala. `http://localhost:8000/management/investment/zones/dc76a5f7-b1ba-4c5d-a9e3-66d1046be78d` | [SDUBeira — Parque Industrial e Logístico](https://sdubeira.co.mz/project/parque-industrial-logistico/); página localmente reconciliada descreve existência/localização e desenvolvimento; pedido HEAD devolveu HTTP 403 nesta sessão. | A fonte reporta 900 ha e 990 ha com âmbitos divergentes; piloto de 50 ha não prova terreno disponível. Relação territorial pendente, sem revisor. **Não pronta.** Resolver âmbito de área e restringir descrição a desenvolvimento reportado; disponibilidade fica “não informada”. | -| `e54d7fb5-5efa-40a3-b145-2827c2a74b2d` / `MOZ-NACALA-ZEE` | Zona Económica Especial de Nacala — Nacala-Porto, província de Nampula. `http://localhost:8000/management/investment/zones/e54d7fb5-5efa-40a3-b145-2827c2a74b2d` | [APIEX — Nacala](https://apiex.gov.mz/nacala-special-economic-zone/); Decreto n.º 76/2007, 18-12-2007. | Fonte jurídica pendente; Nacala-Porto e Nacala-a-Velha estão ambas pendentes como âmbito legal; sem revisor. APIEX inacessível nesta sessão. **Não pronta.** Conferir Decreto 76/2007 e decidir, com evidência, se ambas as localizações pertencem ao âmbito; não as promover por associação automática. | -| `e2e44a13-bb19-48cb-b3da-d716eb6c189b` / `MOZ-REVUBUE-ZFI` | Zona Franca Industrial de Revúbuè — território primário Chiuta, Tete. `http://localhost:8000/management/investment/zones/e2e44a13-bb19-48cb-b3da-d716eb6c189b` | [APIEX — Revúbuè](https://apiex.gov.mz/revubue-free-industrial-zone/) reporta identidade, distritos e área; aponta Decreto n.º 41/2016. | Falta registo estruturado do diploma em `investment_legal_sources`; Chiuta e Moatize estão ambas pendentes; regime/perímetro por confrontar com o Decreto; fonte APIEX inacessível nesta sessão; sem revisor. **Não pronta.** Acrescentar/confirmar referência do Decreto e rever dois distritos separadamente. | -| `0ae14c9a-1adb-4ee7-8094-d5d54d4e75f1` / `MOZ-TOPUITO-PARK` | Parque Industrial de Topuito — território primário Nampula (província; Larde não existe no mestre territorial). `http://localhost:8000/management/investment/zones/0ae14c9a-1adb-4ee7-8094-d5d54d4e75f1` | [MozParks — início de construção do SME Village](https://mozparks.co.mz/construction-begins-on-sme-village-at-topuito-industrial-park/); suporta desenvolvimento, não disponibilidade operacional. Página respondeu HTTP 200. | Localização só provincial; Larde precisa de correspondência territorial documentada. Áreas de 200/215 ha divergem e 30 ha desenvolvidos não são disponibilidade. Relação territorial pendente; sem revisor. **Não pronta** para anúncio de lotes; pode ser proposta informativa após revisão individual com âmbito e construção claramente limitados. | -| `1f442439-fd9a-410c-b1a7-78c4332d31a0` / `MOZ-TOPUITO-ZFI` | Zona Franca Industrial de Topuito — território primário Nampula (a fonte situa-a em Topuito, distrito de Larde). `http://localhost:8000/management/investment/zones/1f442439-fd9a-410c-b1a7-78c4332d31a0` | [INM — BR 190/I Série, Decreto 57/2023](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-2247), art. 1 indicado como candidato; o URL actualmente guardado no registo devolve 404. A ficha oficial identifica criação na localidade de Topuito, distrito de Larde, província de Nampula. | Não existe referência legal estruturada; URL armazenado precisa correcção; Larde falta no mestre territorial; relação territorial pendente; sem revisor. **Não pronta.** Correcção técnica da URL e referência legal, seguida de decisão humana sobre o mapeamento territorial; não atribuir Larde por aproximação. | +| ID / código | Título, localização e URL de gestão | Origem e alcance efectivamente registado | Falta exacta; bloqueio; proposta individual | +| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `1b57649a-f522-4cd5-8e97-32e82b6e1b37` / `MOZ-BELULUANE-PARK` | Parque Industrial de Beluluane — Matola Rio, Boane. `http://localhost:8000/management/investment/zones/1b57649a-f522-4cd5-8e97-32e82b6e1b37` | [APIEX — Beluluane](https://apiex.gov.mz/beluluane-industrial-park-free-zone/); referência institucional, não comprova regime, incentivos ou disponibilidade. Reconciliação local distingue complexo ~780 ha, parque 600 ha, Mozal 180 ha e 55 ha de ZFI; [MozParks](https://mozparks.co.mz/) também reporta área para arrendamento com âmbito diferente. | Relação Matola Rio pendente; perímetros e relação das áreas não reconciliados; não há confirmação de lotes disponíveis. Sem revisor. **Proposta:** pode ser considerado para publicação informativa depois de o gestor confirmar existência/escopo e publicar sem declarar área disponível; manter sem operador se desconhecido. | +| `d25d4eb1-744a-42e1-943a-47d33019c31c` / `MOZ-BELULUANE-ZFI` | Zona Franca Industrial de Beluluane — Matola Rio, Boane. `http://localhost:8000/management/investment/zones/d25d4eb1-744a-42e1-943a-47d33019c31c` | [APIEX — Beluluane](https://apiex.gov.mz/beluluane-industrial-park-free-zone/); referência à Resolução Interna do Conselho de Ministros n.º 15/99, 12-10-1999. | Artigo/efeito legal actual e fonte oficial primária ainda pendentes; `investment_legal_sources.validation_status=pending`; relação territorial pendente; sem revisor. **Não pronta.** Revisor confirma texto e vigência, valida território e só então gestor decide publicação. | +| `0d216b6d-598c-4672-be2a-294e5e924da5` / `MOZ-CDP-ANCUABE` | Parque Industrial de Ancuabe — distrito Ancuabe, Cabo Delgado. `http://localhost:8000/management/investment/zones/0d216b6d-598c-4672-be2a-294e5e924da5` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); suporta associação distrital na iniciativa provincial, não instalação operacional independente. | Fonte de promotor, não primária oficial; relação Ancuabe pendente; estado planeado, sem área/operação/disponibilidade confirmadas; sem revisor. **Não pronta** segundo o requisito de fonte oficial. Decisão: obter fonte oficial adequada ou manter rascunho; se publicar no futuro, rotular “planeado/iniciativa”, não parque operacional. | +| `ed4d2fba-95d5-42e3-8ca7-1f520de1036a` / `MOZ-CDP-BALAMA` | Parque Industrial de Balama — distrito Balama, Cabo Delgado. `http://localhost:8000/management/investment/zones/ed4d2fba-95d5-42e3-8ca7-1f520de1036a` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); sustenta associação à iniciativa, não operação individual. | Fonte oficial individual em falta; relação Balama pendente; sem área, operação ou disponibilidade; sem revisor. **Não pronta**; decisão individual igual à de Ancuabe, sem publicação em massa. | +| `aafbbac1-e8d8-4f33-8f93-d0acedc1ff95` / `MOZ-CDP-MONTEPUEZ` | Parque Industrial de Montepuez — distrito Montepuez, Cabo Delgado. `http://localhost:8000/management/investment/zones/aafbbac1-e8d8-4f33-8f93-d0acedc1ff95` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); associação distrital, não operação individual. | Fonte oficial individual em falta; relação Montepuez pendente; sem área/operação/disponibilidade; sem revisor. **Não pronta**; obter evidência específica ou manter rascunho. | +| `4b002994-b11c-4adf-80f3-12966bf669cb` / `MOZ-CDP-PALMA` | Parque Industrial de Palma — distrito Palma, Cabo Delgado. `http://localhost:8000/management/investment/zones/4b002994-b11c-4adf-80f3-12966bf669cb` | [MozParks — iniciativa de Cabo Delgado](https://mozparks.co.mz/mozparks-launches-cabo-delgado-parks/); associação distrital, não operação individual. | Fonte oficial individual em falta; relação Palma pendente; sem área/operação/disponibilidade; sem revisor. **Não pronta**; obter evidência específica ou manter rascunho. | +| `59fe5844-44de-4fd6-8968-9d23f80a77bb` / `MOZ-CRUSSE-JAMALI-ZEE` | Zona de Estância de Turismo Integrado de Crusse e Jamali — Matibane, Mossuril. `http://localhost:8000/management/investment/zones/59fe5844-44de-4fd6-8968-9d23f80a77bb` | [INM — BR 70/I Série, p. 571](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-70-de-300813-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-571); Decreto n.º 47/2013, 30-08-2013. A página respondeu HTTP 200. | Artigo e efeito legal marcados pendentes no registo; fonte jurídica pendente; relação de âmbito jurídico Matibane pendente; sem revisor. **Não pronta.** Revisor confere decreto/âmbito e vigência; a ausência de operador não é bloqueio. | +| `30f38ba2-3a55-4cb8-8c7b-97a0445c5614` / `MOZ-MANGA-MUNGASSA-ZEE` | Zona Económica Especial de Manga-Mungassa — Cidade da Beira, Sofala. `http://localhost:8000/management/investment/zones/30f38ba2-3a55-4cb8-8c7b-97a0445c5614` | [APIEX — Manga-Mungassa](https://apiex.gov.mz/zee/manga-mungassa-special-economic-zone/) e Decreto n.º 22/2012, 06-06-2012. | Artigo/efeito legal pendente; URL APIEX não respondeu nesta sessão; fonte legal permanece pending; relação territorial pendente; sem revisor. **Não pronta.** Revisor confirma o acto em fonte primária acessível; publicação não depende de operador identificado. | +| `e67a94bc-7d75-4c03-8b66-0bbe7ad034bf` / `MOZ-MINHEWENE-ZFI` | Zona Franca Industrial de Minhewene — Nampula (apenas província). `http://localhost:8000/management/investment/zones/e67a94bc-7d75-4c03-8b66-0bbe7ad034bf` | [APIEX — Minhewene](https://apiex.gov.mz/zona-franca-industrial-de-minhewene/), declara identidade, província e área e aponta Decreto n.º 51/2011; a fonte foi inacessível nesta verificação. | Não existe `legal_source_id` para o diploma; localização mais precisa que província por confirmar; relação territorial pendente; sem revisor. **Não pronta.** Criar/validar referência ao Decreto 51/2011 e confirmar território antes de publicar como ZFI. | +| `74c5dab4-afa7-4f36-8f75-7df0a900b4fe` / `MOZ-MOCUBA-ZEE` | Zona Económica Especial de Mocuba — distrito Mocuba, Zambézia. `http://localhost:8000/management/investment/zones/74c5dab4-afa7-4f36-8f75-7df0a900b4fe` | [INM — BR 46/I Série, p. 1259](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-46-de-060614-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1259), Decreto n.º 28/2014. A página respondeu HTTP 200. | Artigo/efeito legal por confirmar; relações Mocuba e Munhamade (duas localizações distintas) pendentes; fonte jurídica pendente; sem revisor. **Não pronta.** Rever as duas localizações contra o diploma separadamente; não fundir com a ZFI de Mocuba. | +| `530e738e-8b9a-429c-b00e-e5135394acd4` / `MOZ-MOCUBA-ZFI` | Zona Franca Industrial de Mocuba — Cidade de Mocuba. `http://localhost:8000/management/investment/zones/530e738e-8b9a-429c-b00e-e5135394acd4` | [APIEX — Mocuba](https://apiex.gov.mz/zif/mocuba-free-industrial-zone/); Decreto n.º 29/2014, 06-06-2014. | Fonte APIEX inacessível nesta sessão; diploma/artigo e vigência pendentes; relação territorial pendente; fonte legal pendente; sem revisor. **Não pronta.** Rever diploma separado da ZEE, confirmar território e só depois publicar. | +| `dc76a5f7-b1ba-4c5d-a9e3-66d1046be78d` / `MOZ-MUNHAVA-PARK` | Parque Industrial e Logístico de Munhava — Cidade da Beira, Sofala. `http://localhost:8000/management/investment/zones/dc76a5f7-b1ba-4c5d-a9e3-66d1046be78d` | [SDUBeira — Parque Industrial e Logístico](https://sdubeira.co.mz/project/parque-industrial-logistico/); página localmente reconciliada descreve existência/localização e desenvolvimento; pedido HEAD devolveu HTTP 403 nesta sessão. | A fonte reporta 900 ha e 990 ha com âmbitos divergentes; piloto de 50 ha não prova terreno disponível. Relação territorial pendente, sem revisor. **Não pronta.** Resolver âmbito de área e restringir descrição a desenvolvimento reportado; disponibilidade fica “não informada”. | +| `e54d7fb5-5efa-40a3-b145-2827c2a74b2d` / `MOZ-NACALA-ZEE` | Zona Económica Especial de Nacala — Nacala-Porto, província de Nampula. `http://localhost:8000/management/investment/zones/e54d7fb5-5efa-40a3-b145-2827c2a74b2d` | [APIEX — Nacala](https://apiex.gov.mz/nacala-special-economic-zone/); Decreto n.º 76/2007, 18-12-2007. | Fonte jurídica pendente; Nacala-Porto e Nacala-a-Velha estão ambas pendentes como âmbito legal; sem revisor. APIEX inacessível nesta sessão. **Não pronta.** Conferir Decreto 76/2007 e decidir, com evidência, se ambas as localizações pertencem ao âmbito; não as promover por associação automática. | +| `e2e44a13-bb19-48cb-b3da-d716eb6c189b` / `MOZ-REVUBUE-ZFI` | Zona Franca Industrial de Revúbuè — território primário Chiuta, Tete. `http://localhost:8000/management/investment/zones/e2e44a13-bb19-48cb-b3da-d716eb6c189b` | [APIEX — Revúbuè](https://apiex.gov.mz/revubue-free-industrial-zone/) reporta identidade, distritos e área; aponta Decreto n.º 41/2016. | Falta registo estruturado do diploma em `investment_legal_sources`; Chiuta e Moatize estão ambas pendentes; regime/perímetro por confrontar com o Decreto; fonte APIEX inacessível nesta sessão; sem revisor. **Não pronta.** Acrescentar/confirmar referência do Decreto e rever dois distritos separadamente. | +| `0ae14c9a-1adb-4ee7-8094-d5d54d4e75f1` / `MOZ-TOPUITO-PARK` | Parque Industrial de Topuito — território primário Nampula (província; Larde não existe no mestre territorial). `http://localhost:8000/management/investment/zones/0ae14c9a-1adb-4ee7-8094-d5d54d4e75f1` | [MozParks — início de construção do SME Village](https://mozparks.co.mz/construction-begins-on-sme-village-at-topuito-industrial-park/); suporta desenvolvimento, não disponibilidade operacional. Página respondeu HTTP 200. | Localização só provincial; Larde precisa de correspondência territorial documentada. Áreas de 200/215 ha divergem e 30 ha desenvolvidos não são disponibilidade. Relação territorial pendente; sem revisor. **Não pronta** para anúncio de lotes; pode ser proposta informativa após revisão individual com âmbito e construção claramente limitados. | +| `1f442439-fd9a-410c-b1a7-78c4332d31a0` / `MOZ-TOPUITO-ZFI` | Zona Franca Industrial de Topuito — território primário Nampula (a fonte situa-a em Topuito, distrito de Larde). `http://localhost:8000/management/investment/zones/1f442439-fd9a-410c-b1a7-78c4332d31a0` | [INM — BR 190/I Série, Decreto 57/2023](https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-br-n%C2%BA-190-de-031023-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-2247), art. 1 indicado como candidato; o URL actualmente guardado no registo devolve 404. A ficha oficial identifica criação na localidade de Topuito, distrito de Larde, província de Nampula. | Não existe referência legal estruturada; URL armazenado precisa correcção; Larde falta no mestre territorial; relação territorial pendente; sem revisor. **Não pronta.** Correcção técnica da URL e referência legal, seguida de decisão humana sobre o mapeamento territorial; não atribuir Larde por aproximação. | ### QA, estados de conclusão e sequência concreta diff --git a/docs/investmoz/delivery-gaps.md b/docs/investmoz/delivery-gaps.md index 4a65c567..391d35d3 100644 --- a/docs/investmoz/delivery-gaps.md +++ b/docs/investmoz/delivery-gaps.md @@ -21,17 +21,17 @@ Backup/restauro verificado antes desta correcção: `storage/app/private/zones-b ## Trabalho que continua por terminar -| Área | Lacuna confirmada nos relatórios/código | Trabalho necessário | -| --- | --- | --- | -| GIS completo | Limites geográficos versionados, transformação/validação de geometrias, endpoints resolve/near/bbox e cobertura espacial não entregues integralmente | Implementar serviços e contratos; carregar geometrias apenas quando fornecidas, sem inventar coordenadas | -| Integração territorial transversal | A nova associação é principal; os consumidores educativos, organizações e restantes formulários não usam ainda todos o mesmo selector; relações múltiplas legadas continuam separadas | Consolidar consumidores e testar cada jornada, incluindo históricos e remoção de referências | -| Importação territorial | CSV auditado, mas não existe ainda o workflow integral de batches/rejeições/aprovação/carga; PDF histórico tem colunas ambíguas | Terminar pipeline, excepções documentadas e reconciliação dos códigos; não tratar aprovação como reparação automática de dados contraditórios | -| IA | Base lexical/determinística; não há modelo generativo, embeddings, reranker ou benchmark generativo | Implementar RAG e avaliação com corpus autorizado; documentar recursos e fornecedor efectivamente usados | -| Mercado de Negócios | Preparação/submissão privada e objectos tipados; publicação, revisão completa, sala documental segura e integrações não concluídas | Implementar os estados/jornadas restantes, acesso documental, scanner, retenção e testes | -| ZEE/ZFI | Núcleo e filtros existentes; cadastro local de zonas continua vazio; modelo integral de actividades, benefícios, obrigações, lotes e indicadores não entregue | Completar domínio e importar conteúdos disponíveis com origem; não substituir dados ausentes por zonas fictícias | -| Educação | Há implementação parcial; metodologia, consumidores, integração e piloto não estão integralmente entregues | Confrontar decisões posteriores com o relatório histórico, executar lacunas técnicas e testar o fluxo completo | -| CRM/integrações | A integração externa real com SuiteCRM e a consolidação da outra máquina não foram executadas | Validar ligação quando disponível; a consolidação da outra máquina foi expressamente adiada pelo utilizador | -| Operação/QA | Testes automáticos e alguns percursos de navegador não cobrem todos os prompts; carga, acessibilidade assistiva e produção não demonstradas | Matriz de aceitação por jornada, testes com dados reais, ensaios operacionais e disponibilização no destino configurado | +| Área | Lacuna confirmada nos relatórios/código | Trabalho necessário | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| GIS completo | Limites geográficos versionados, transformação/validação de geometrias, endpoints resolve/near/bbox e cobertura espacial não entregues integralmente | Implementar serviços e contratos; carregar geometrias apenas quando fornecidas, sem inventar coordenadas | +| Integração territorial transversal | A nova associação é principal; os consumidores educativos, organizações e restantes formulários não usam ainda todos o mesmo selector; relações múltiplas legadas continuam separadas | Consolidar consumidores e testar cada jornada, incluindo históricos e remoção de referências | +| Importação territorial | CSV auditado, mas não existe ainda o workflow integral de batches/rejeições/aprovação/carga; PDF histórico tem colunas ambíguas | Terminar pipeline, excepções documentadas e reconciliação dos códigos; não tratar aprovação como reparação automática de dados contraditórios | +| IA | Base lexical/determinística; não há modelo generativo, embeddings, reranker ou benchmark generativo | Implementar RAG e avaliação com corpus autorizado; documentar recursos e fornecedor efectivamente usados | +| Mercado de Negócios | Preparação/submissão privada e objectos tipados; publicação, revisão completa, sala documental segura e integrações não concluídas | Implementar os estados/jornadas restantes, acesso documental, scanner, retenção e testes | +| ZEE/ZFI | Núcleo e filtros existentes; cadastro local de zonas continua vazio; modelo integral de actividades, benefícios, obrigações, lotes e indicadores não entregue | Completar domínio e importar conteúdos disponíveis com origem; não substituir dados ausentes por zonas fictícias | +| Educação | Há implementação parcial; metodologia, consumidores, integração e piloto não estão integralmente entregues | Confrontar decisões posteriores com o relatório histórico, executar lacunas técnicas e testar o fluxo completo | +| CRM/integrações | A integração externa real com SuiteCRM e a consolidação da outra máquina não foram executadas | Validar ligação quando disponível; a consolidação da outra máquina foi expressamente adiada pelo utilizador | +| Operação/QA | Testes automáticos e alguns percursos de navegador não cobrem todos os prompts; carga, acessibilidade assistiva e produção não demonstradas | Matriz de aceitação por jornada, testes com dados reais, ensaios operacionais e disponibilização no destino configurado | Estas lacunas não são todas bloqueios de autorização. A maior parte corresponde a implementação que ficou por fazer. Dependências de dados, de ligações externas e de recursos de execução devem ser identificadas separadamente; não servem para suspender trabalho independente já autorizado. diff --git a/docs/investmoz/document-corpus-reconciliation-2026-10-06.md b/docs/investmoz/document-corpus-reconciliation-2026-10-06.md index a2021423..795d2fb6 100644 --- a/docs/investmoz/document-corpus-reconciliation-2026-10-06.md +++ b/docs/investmoz/document-corpus-reconciliation-2026-10-06.md @@ -4,21 +4,21 @@ Data: 06-10-2026. Inventário limitado aos anexos enumerados no manifesto local ## Reconciliação quantitativa -| Medida | Resultado desta revisão | -|---|---:| -| Anexos enumerados no manifesto | 52 ficheiros `.txt` | -| Conteúdos de anexo únicos por SHA-256 | 50 | -| Pares de anexos exactamente duplicados | 2 pares: `1b36f6f0…` / `27922f5b…` e `11c236f7…` / `f003a1f1…` | -| Caminhos de ficheiros acessíveis inventariados | 67 | -| Conteúdos únicos por SHA-256 entre esses caminhos | 59 | -| Documentos-fonte substantivos únicos acessíveis | 4 | -| Documentos binários de fonte actualmente acessíveis | 4 documentos distintos: Lei 22/2022, BR 248/2025, BR 89/2017 e Codificador DPA do INE | -| Cópias/derivados privados adicionais inventariados | 11 objectos, incluindo cópias dos quatro PDFs, extracções e artefactos internos de revisão | -| Registos documentais importados na base activa | 4 documentos e 4 recursos; todos com scan limpo e hash do scan igual ao hash armazenado | -| Publicados para consulta pública | 0 | -| Privados na biblioteca activa | 4 | +| Medida | Resultado desta revisão | +| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------: | +| Anexos enumerados no manifesto | 52 ficheiros `.txt` | +| Conteúdos de anexo únicos por SHA-256 | 50 | +| Pares de anexos exactamente duplicados | 2 pares: `1b36f6f0…` / `27922f5b…` e `11c236f7…` / `f003a1f1…` | +| Caminhos de ficheiros acessíveis inventariados | 67 | +| Conteúdos únicos por SHA-256 entre esses caminhos | 59 | +| Documentos-fonte substantivos únicos acessíveis | 4 | +| Documentos binários de fonte actualmente acessíveis | 4 documentos distintos: Lei 22/2022, BR 248/2025, BR 89/2017 e Codificador DPA do INE | +| Cópias/derivados privados adicionais inventariados | 11 objectos, incluindo cópias dos quatro PDFs, extracções e artefactos internos de revisão | +| Registos documentais importados na base activa | 4 documentos e 4 recursos; todos com scan limpo e hash do scan igual ao hash armazenado | +| Publicados para consulta pública | 0 | +| Privados na biblioteca activa | 4 | | Fontes anteriormente referidas mas inacessíveis nesta execução | 10 entradas, cada uma listada no CSV com caminho/nome tal como registado e pendência concreta | -| Registos no CSV de inventário | 77 objectos/entradas; inclui anexos, originais, cópias, derivados e fontes inacessíveis | +| Registos no CSV de inventário | 77 objectos/entradas; inclui anexos, originais, cópias, derivados e fontes inacessíveis | Os 52 anexos são instruções, prompts, texto de terminal/SQL e outros conteúdos colados; não são 52 diplomas. Foram preservados como histórico, fora da biblioteca pública. Dois pares têm bytes idênticos; as restantes cópias mantêm-se individualizadas. Os 59 hashes únicos incluem 50 textos de instrução, quatro fontes substantivas e cinco conteúdos auxiliares únicos; cópias privadas não contam como documentos novos. O CSV não copia o conteúdo potencialmente privado dos anexos ou dos artefactos de revisão. @@ -28,12 +28,12 @@ Os 10 itens inacessíveis são as sete fontes antigas referidas por caminho rela O lote real de quatro fontes foi previamente importado para a biblioteca activa, com cópia privada, hash e análise antimalware. A pré-visualização repetida de `php artisan documents:import-legal-source-archive` nesta execução encontrou os quatro hashes já registados e devolveu `reused/reconciled` para cada um; terminou com “Preview only; no files or database rows were changed.” Não criou cópias nem alterou a base activa. -| Recurso | Disponibilidade | Situação documental | Uso fiscal | -|---:|---|---|---| -| 1 — Lei n.º 22/2022, CIVA republicado | Privado; consulta pública pendente da acção autenticada de disponibilização | Fonte oficial e bytes confirmados; diploma alterador; edição/data visíveis; não equivale a consolidação integral actual | Art. 17(1) citado como evidência documental; não activa taxa nem cálculo | -| 2 — BR n.º 248/2025, Suplemento n.º 1 | Privado; consulta pública pendente da mesma acção autenticada | Edição oficial confirmada; contém seis diplomas distintos; vigência deve ser tratada por diploma/dispositivo | Citações ligadas às chaves técnicas como evidência; sem activação de regras | -| 3 — BR n.º 89/2017, aviso de rectificação | Privado; autorização anterior permite disponibilizar como cópia fornecida | A ficha INM identifica o aviso, mas os bytes/anexos da cópia não foram comparados com exemplar oficial; autenticidade da cópia não confirmada | Associação ao CIVA fica pendente; não é fundamento confirmado | -| 4 — Codificador DPA INE, edição 07-06-2012 | Privado; consulta pública pendente da mesma acção autenticada | PDF comparado com exemplar servido pelo INE; actualidade territorial e direitos do artefacto PDF não determinam a autenticidade da edição | Sem uso como fonte fiscal; não usar automaticamente como cadastro territorial actual | +| Recurso | Disponibilidade | Situação documental | Uso fiscal | +| -----------------------------------------: | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| 1 — Lei n.º 22/2022, CIVA republicado | Privado; consulta pública pendente da acção autenticada de disponibilização | Fonte oficial e bytes confirmados; diploma alterador; edição/data visíveis; não equivale a consolidação integral actual | Art. 17(1) citado como evidência documental; não activa taxa nem cálculo | +| 2 — BR n.º 248/2025, Suplemento n.º 1 | Privado; consulta pública pendente da mesma acção autenticada | Edição oficial confirmada; contém seis diplomas distintos; vigência deve ser tratada por diploma/dispositivo | Citações ligadas às chaves técnicas como evidência; sem activação de regras | +| 3 — BR n.º 89/2017, aviso de rectificação | Privado; autorização anterior permite disponibilizar como cópia fornecida | A ficha INM identifica o aviso, mas os bytes/anexos da cópia não foram comparados com exemplar oficial; autenticidade da cópia não confirmada | Associação ao CIVA fica pendente; não é fundamento confirmado | +| 4 — Codificador DPA INE, edição 07-06-2012 | Privado; consulta pública pendente da mesma acção autenticada | PDF comparado com exemplar servido pelo INE; actualidade territorial e direitos do artefacto PDF não determinam a autenticidade da edição | Sem uso como fonte fiscal; não usar automaticamente como cadastro territorial actual | A autorização anterior está registada com proveniência `user-instruction`, sem atribuição a gestor fictício, e abrange os recursos 1–4. Não exige nova aprovação genérica. O mecanismo de publicação permanece protegido por autenticação, email verificado e permissão de gestão. A acção preparada está em `/management/portal-services`, botão **“Aplicar autorização e disponibilizar documentos elegíveis”**; cada item é verificado individualmente, o executor autenticado fica registado e nenhuma regra fiscal é alterada. A consulta pública só estará concluída depois dessa acção ser executada e confirmada em `/documentos`. @@ -51,17 +51,17 @@ As 25 referências cobrem: catálogos da AT e INSS; cadeia do CIVA (Leis 3/2012, Fontes primárias presentes no acervo activo: Lei 22/2022, BR 248/2025 e BR 89/2017. O código de importação/revisão identifica a Lei 22/2022, art. 17(1), e as Leis 7–12/2025 no BR 248; as relações activas à data desta leitura são somente tags gerais pendentes, não relações persistidas a requisitos `fiscal.*`. A associação candidata do BR 89/2017 também permanece pendente. A identificação de artigos é evidência documental, não validação da incidência, vigência integral ou fórmula. -| Componente | Fonte localizada/ligada | Lacuna documental ou de regra | Estado de uso | -|---|---|---|---| -| IVA | Lei 22/2022 art. 17(1); Lei 10/2025 e Lei 3/2025 dentro do BR 248; cadeia de 2016/2017 descrita no registo oficial | O PDF da republicação integral Lei 13/2016 e cópia legível/autêntica dos anexos rectificados continuam inacessíveis; faltam os binários/efeitos consolidados das alterações 2020 e regulamentos publicados/anunciados em 2026 | Um ponto textual de 16% está identificado no documento; sem cálculo fiscal integrado | -| IRPC | Lei 12/2025, arts. e dispositivos indicados na revisão do BR 248 | Código-base consolidado, regulamentos 2026, retenções, mais-valias digitais/estabelecimento estável por contexto e casos dourados completos não estão arquivados neste lote | Sem regra activada | -| ISPC | Lei 9/2025 e republicação anexa da Lei 5/2009 no BR 248 | Diploma-base/regulamento integral legível e alterações regulamentares de 2026; elegibilidade e exclusões por operação ainda carecem de consolidação | Sem regra activada; não confundir com regimes simplificados de IVA/IRPS | -| IRPS | Lei 11/2025 no BR 248 | Código-base consolidado, regulamentos 2026, residência e categorias com alcance/obrigações por período | Sem regra activada; retenção mensal não representa apuramento anual | -| INSS | Referências externas ao RSSO Decreto 51/2017 e alterações, incluindo Decreto 56/2024, em registo de fontes | Nenhum diploma binário está no arquivo fiscal; taxas/base do regime geral e TCP, limites e vigência ainda não foram conferidos | Sem regra activada; taxas 4%/3% continuam sintéticas/não aprovadas | -| Retenções | Dispositivos diversos nas Leis 11/2025 e 12/2025 citadas no BR 248 | Falta matriz por natureza, residência, convenção, evento de retenção, documento e vigência; não há fundamento suficiente para uma regra universal | Sem regra activada | -| ICE | Lei 7/2025, incluindo tabela anexa, citada no BR 248 | Código completo, produto/unidade/base/taxa, transições e regulamentação recente não estão reconciliados em arquivo executável | Sem regra activada | -| Aduaneiro | Lei 8/2025 e tabela anexa citadas no BR 248 | Pauta de serviço 2026 por código, origem/preferência, classificação, unidade, câmbio aduaneiro e regras da base de IVA importação não foram arquivados e comparados campo a campo | Sem regra activada; sem código pautal não calcular DA | -| Benefícios/incentivos | Lei 4/2009 é citada por cópia consolidada antiga em inventário anterior, actualmente inacessível | Regime, certificado, actividade, área, período e elegibilidade não têm fontes completas no arquivo disponível | Não há concessão nem benefício automático | +| Componente | Fonte localizada/ligada | Lacuna documental ou de regra | Estado de uso | +| --------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| IVA | Lei 22/2022 art. 17(1); Lei 10/2025 e Lei 3/2025 dentro do BR 248; cadeia de 2016/2017 descrita no registo oficial | O PDF da republicação integral Lei 13/2016 e cópia legível/autêntica dos anexos rectificados continuam inacessíveis; faltam os binários/efeitos consolidados das alterações 2020 e regulamentos publicados/anunciados em 2026 | Um ponto textual de 16% está identificado no documento; sem cálculo fiscal integrado | +| IRPC | Lei 12/2025, arts. e dispositivos indicados na revisão do BR 248 | Código-base consolidado, regulamentos 2026, retenções, mais-valias digitais/estabelecimento estável por contexto e casos dourados completos não estão arquivados neste lote | Sem regra activada | +| ISPC | Lei 9/2025 e republicação anexa da Lei 5/2009 no BR 248 | Diploma-base/regulamento integral legível e alterações regulamentares de 2026; elegibilidade e exclusões por operação ainda carecem de consolidação | Sem regra activada; não confundir com regimes simplificados de IVA/IRPS | +| IRPS | Lei 11/2025 no BR 248 | Código-base consolidado, regulamentos 2026, residência e categorias com alcance/obrigações por período | Sem regra activada; retenção mensal não representa apuramento anual | +| INSS | Referências externas ao RSSO Decreto 51/2017 e alterações, incluindo Decreto 56/2024, em registo de fontes | Nenhum diploma binário está no arquivo fiscal; taxas/base do regime geral e TCP, limites e vigência ainda não foram conferidos | Sem regra activada; taxas 4%/3% continuam sintéticas/não aprovadas | +| Retenções | Dispositivos diversos nas Leis 11/2025 e 12/2025 citadas no BR 248 | Falta matriz por natureza, residência, convenção, evento de retenção, documento e vigência; não há fundamento suficiente para uma regra universal | Sem regra activada | +| ICE | Lei 7/2025, incluindo tabela anexa, citada no BR 248 | Código completo, produto/unidade/base/taxa, transições e regulamentação recente não estão reconciliados em arquivo executável | Sem regra activada | +| Aduaneiro | Lei 8/2025 e tabela anexa citadas no BR 248 | Pauta de serviço 2026 por código, origem/preferência, classificação, unidade, câmbio aduaneiro e regras da base de IVA importação não foram arquivados e comparados campo a campo | Sem regra activada; sem código pautal não calcular DA | +| Benefícios/incentivos | Lei 4/2009 é citada por cópia consolidada antiga em inventário anterior, actualmente inacessível | Regime, certificado, actividade, área, período e elegibilidade não têm fontes completas no arquivo disponível | Não há concessão nem benefício automático | O registo `simulation_tax_rule_versions` não mostrou versões no contexto de leitura da base activa. Independentemente dessa consulta, `RunSimulation` não invoca `SimulationTaxParameterResolver` nem `SimulationTaxRuleExpression` para calcular IRPC, IVA, ISPC, IRPS, INSS, retenções ou aduaneiro; a aritmética genérica introduzida pelo utilizador continua separada. A governança DSL/DAG, o resolvedor temporal e os cenários de QA não são regras fiscais activas. Não alterei cálculos, parâmetros, snapshots ou resultados nesta revisão. diff --git a/docs/investmoz/document-library-completion-2026-10-06.md b/docs/investmoz/document-library-completion-2026-10-06.md index d55ec96e..66dc4fe7 100644 --- a/docs/investmoz/document-library-completion-2026-10-06.md +++ b/docs/investmoz/document-library-completion-2026-10-06.md @@ -10,12 +10,12 @@ Foram importados quatro documentos para armazenamento privado. Todos permanecem ## Inventário e decisões pendentes por documento -| Documento | Proveniência e estado | Relações | Decisão pendente antes de publicar | -|---|---|---|---| -| Lei n.º 22/2022, republicação do Código do IVA | PDF comparado byte a byte com o exemplar da AT; origem oficial confirmada. Estado jurídico registado como alterado, não como consolidação integral actual. | Evidência documental do simulador fiscal. | Gestor deve rever a possibilidade de divulgação da composição PDF e confirmar a versão/alcance editorial. Direitos: `unconfirmed`; classificação: `internal`. | -| BR n.º 248/2025, Suplemento n.º 1 | PDF comparado byte a byte com endpoint oficial da AT. Reúne vários diplomas; estado jurídico agregado desconhecido. | Evidência documental fiscal, sem afirmar que cada tema cria obrigação ou regra activa. | Avaliar direitos da composição e identificar os diplomas e vigências individualmente antes de apresentar como referência consolidada. | -| BR n.º 89, I Série, de 08/06/2017 | A ficha do INM identifica o aviso de rectificação da Lei n.º 13/2016 na p. 569; o binário local não foi comparado com um download oficial. Proveniência apenas identificada. | Evidência fiscal; efeito temporal continua por confirmar. | Obter o PDF/binário oficial, comparar hash e confirmar autenticidade, anexos e efeito da rectificação. Direitos continuam por confirmar. | -| Codificador da Divisão Político-Administrativa do INE, edição de 2012 | Os bytes locais (67.067 bytes) coincidem com o PDF servido pelo URL oficial do INE. Confirma a origem do ficheiro, não a actualidade dos códigos nem os direitos de reutilização da composição. | Sem associação automática às relações territoriais actuais. | Rever reutilização/publicação e utilidade histórica. Não usar esta edição para atribuir localizações actuais sem reconciliação documentada. | +| Documento | Proveniência e estado | Relações | Decisão pendente antes de publicar | +| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Lei n.º 22/2022, republicação do Código do IVA | PDF comparado byte a byte com o exemplar da AT; origem oficial confirmada. Estado jurídico registado como alterado, não como consolidação integral actual. | Evidência documental do simulador fiscal. | Gestor deve rever a possibilidade de divulgação da composição PDF e confirmar a versão/alcance editorial. Direitos: `unconfirmed`; classificação: `internal`. | +| BR n.º 248/2025, Suplemento n.º 1 | PDF comparado byte a byte com endpoint oficial da AT. Reúne vários diplomas; estado jurídico agregado desconhecido. | Evidência documental fiscal, sem afirmar que cada tema cria obrigação ou regra activa. | Avaliar direitos da composição e identificar os diplomas e vigências individualmente antes de apresentar como referência consolidada. | +| BR n.º 89, I Série, de 08/06/2017 | A ficha do INM identifica o aviso de rectificação da Lei n.º 13/2016 na p. 569; o binário local não foi comparado com um download oficial. Proveniência apenas identificada. | Evidência fiscal; efeito temporal continua por confirmar. | Obter o PDF/binário oficial, comparar hash e confirmar autenticidade, anexos e efeito da rectificação. Direitos continuam por confirmar. | +| Codificador da Divisão Político-Administrativa do INE, edição de 2012 | Os bytes locais (67.067 bytes) coincidem com o PDF servido pelo URL oficial do INE. Confirma a origem do ficheiro, não a actualidade dos códigos nem os direitos de reutilização da composição. | Sem associação automática às relações territoriais actuais. | Rever reutilização/publicação e utilidade histórica. Não usar esta edição para atribuir localizações actuais sem reconciliação documentada. | Nos quatro registos, a análise antimalware foi concluída sem ameaça e os hashes analisados coincidem com os ficheiros guardados. Este resultado é uma verificação técnica, não prova de direitos de publicação, exactidão jurídica, vigência ou actualidade. diff --git a/docs/investmoz/document-library-inventory-2026-10-05.md b/docs/investmoz/document-library-inventory-2026-10-05.md index 3cc99041..a914e990 100644 --- a/docs/investmoz/document-library-inventory-2026-10-05.md +++ b/docs/investmoz/document-library-inventory-2026-10-05.md @@ -16,11 +16,11 @@ Uma consulta apenas de leitura à base configurada como `investemoz`, juntando ` Os três ficheiros abaixo estão em `docs/investmoz/simulations/source-archive/`. O inventário registou nome, tamanho e hash; esta tarefa **não confirmou o texto integral, fonte oficial, vigência, idioma, direitos de reprodução, âmbito ou aptidão para publicação**. Permanecem no arquivo original e não foram importados para o catálogo. -| Ficheiro | Bytes | SHA-256 | Situação para a biblioteca | -|---|---:|---|---| +| Ficheiro | Bytes | SHA-256 | Situação para a biblioteca | +| ----------------------------------------------------- | --------: | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | | `Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf` | 1.834.284 | `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53` | Candidato a legislação; fonte canónica, artigos, vigência consolidada e direitos por rever. | -| `BR-89-I-Serie-2017-06-08.pdf` | 9.456.358 | `11F0CAB15EB6E51010952203FFA24FDC533A24FD642E19525FE4F46B17A1F5E5` | Boletim da República; acto/artigos e relação com diplomas por identificar. | -| `BR-248-I-Suplemento-1-2025.pdf` | 440.018 | `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251` | Boletim da República; acto/artigos e relação com diplomas por identificar. | +| `BR-89-I-Serie-2017-06-08.pdf` | 9.456.358 | `11F0CAB15EB6E51010952203FFA24FDC533A24FD642E19525FE4F46B17A1F5E5` | Boletim da República; acto/artigos e relação com diplomas por identificar. | +| `BR-248-I-Suplemento-1-2025.pdf` | 440.018 | `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251` | Boletim da República; acto/artigos e relação com diplomas por identificar. | ## Exclusões e limites do inventário diff --git a/docs/investmoz/document-requirement-associations-2026-10-06.md b/docs/investmoz/document-requirement-associations-2026-10-06.md index 508325a2..815cdbf9 100644 --- a/docs/investmoz/document-requirement-associations-2026-10-06.md +++ b/docs/investmoz/document-requirement-associations-2026-10-06.md @@ -12,14 +12,14 @@ A migração aditiva `2026_10_06_200000_add_document_requirement_evidence_links` O PDF de 21 páginas foi descarregado do catálogo/WebDAV oficial da Autoridade Tributária e comparado com a cópia arquivada: 440.018 bytes e SHA-256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. A ficha da Imprensa Nacional identifica a edição. A leitura directa foi feita no texto extraído do próprio PDF, com localização por página. O número do boletim, isoladamente, não é usado como referência de requisito. -| Chave técnica | Diploma identificado dentro do BR | Dispositivos citados no texto primário | Páginas PDF | Estado da associação | -|---|---|---|---:|---| -| `fiscal.ice.lei_7_2025` | Lei n.º 7/2025, de 29 de Dezembro, alteração ao Código do ICE | Lei, arts. 1–5 e tabela anexa; art. 1 prorroga a aplicação das taxas até 2027 e art. 5 indica entrada em vigor em 01/01/2026 | 1–2 | Citação localizada; vigência consolidada, regulamentação, alcance da tabela e aprovação fiscal pendentes | -| `fiscal.customs.lei_8_2025` | Lei n.º 8/2025, de 29 de Dezembro, alteração à Pauta Aduaneira e Instruções Preliminares | Lei, art. 1 altera os arts. 21(2), 22 e 27 das Instruções Preliminares; Lei, arts. 2–5 e tabela anexa | 2–7 | Citação localizada; classificação por código/origem, preferências, regulamentação e aplicação a operações concretas pendentes | -| `fiscal.ispc.lei_9_2025` | Lei n.º 9/2025, de 29 de Dezembro, alteração ao ISPC | Lei, arts. 1–7 e republicação anexa da Lei n.º 5/2009 | 8–11 | Citação localizada; interpretação, regulamento, elegibilidade e aprovação fiscal pendentes | -| `fiscal.iva.lei_10_2025` | Lei n.º 10/2025, de 29 de Dezembro, alteração ao CIVA | Lei, arts. 1–4; no texto alterado encontram-se, entre outros, CIVA arts. 18, 19, 21, 23, 25-A, 27 e 32–34 | 12–15 | Citação localizada; consolidação, regulamentação, vigência por dispositivo e aprovação fiscal pendentes | -| `fiscal.irps.lei_11_2025` | Lei n.º 11/2025, de 29 de Dezembro, alteração ao CIRPS | Lei, arts. 1–4; o texto alterado inclui CIRPS arts. 21, 22, 48, 50, 54-A, 57, 65 e 65-A | 16–18 | Citação localizada; alcance, transições, regulamentos e aprovação fiscal pendentes | -| `fiscal.irpc.lei_12_2025` | Lei n.º 12/2025, de 29 de Dezembro, alteração ao CIRPC | Lei, arts. 1–4; o texto alterado inclui CIRPC arts. 3, 5, 20, 62, 67 e 75; Lei, art. 2 adita o CIRPC art. 61-A | 19–20 | Citação localizada; alcance, transições, regulamentos e aprovação fiscal pendentes | +| Chave técnica | Diploma identificado dentro do BR | Dispositivos citados no texto primário | Páginas PDF | Estado da associação | +| --------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------: | ----------------------------------------------------------------------------------------------------------------------------- | +| `fiscal.ice.lei_7_2025` | Lei n.º 7/2025, de 29 de Dezembro, alteração ao Código do ICE | Lei, arts. 1–5 e tabela anexa; art. 1 prorroga a aplicação das taxas até 2027 e art. 5 indica entrada em vigor em 01/01/2026 | 1–2 | Citação localizada; vigência consolidada, regulamentação, alcance da tabela e aprovação fiscal pendentes | +| `fiscal.customs.lei_8_2025` | Lei n.º 8/2025, de 29 de Dezembro, alteração à Pauta Aduaneira e Instruções Preliminares | Lei, art. 1 altera os arts. 21(2), 22 e 27 das Instruções Preliminares; Lei, arts. 2–5 e tabela anexa | 2–7 | Citação localizada; classificação por código/origem, preferências, regulamentação e aplicação a operações concretas pendentes | +| `fiscal.ispc.lei_9_2025` | Lei n.º 9/2025, de 29 de Dezembro, alteração ao ISPC | Lei, arts. 1–7 e republicação anexa da Lei n.º 5/2009 | 8–11 | Citação localizada; interpretação, regulamento, elegibilidade e aprovação fiscal pendentes | +| `fiscal.iva.lei_10_2025` | Lei n.º 10/2025, de 29 de Dezembro, alteração ao CIVA | Lei, arts. 1–4; no texto alterado encontram-se, entre outros, CIVA arts. 18, 19, 21, 23, 25-A, 27 e 32–34 | 12–15 | Citação localizada; consolidação, regulamentação, vigência por dispositivo e aprovação fiscal pendentes | +| `fiscal.irps.lei_11_2025` | Lei n.º 11/2025, de 29 de Dezembro, alteração ao CIRPS | Lei, arts. 1–4; o texto alterado inclui CIRPS arts. 21, 22, 48, 50, 54-A, 57, 65 e 65-A | 16–18 | Citação localizada; alcance, transições, regulamentos e aprovação fiscal pendentes | +| `fiscal.irpc.lei_12_2025` | Lei n.º 12/2025, de 29 de Dezembro, alteração ao CIRPC | Lei, arts. 1–4; o texto alterado inclui CIRPC arts. 3, 5, 20, 62, 67 e 75; Lei, art. 2 adita o CIRPC art. 61-A | 19–20 | Citação localizada; alcance, transições, regulamentos e aprovação fiscal pendentes | As referências de página/artigo acima foram conferidas no próprio PDF. “Citação localizada” não afirma que todos os dispositivos citados incidam sobre qualquer utilizador, que toda a lei continue vigente sem alterações, nem que exista uma obrigação executável. As seis ligações aparecem no sistema como `applicability=evidence`. @@ -27,12 +27,12 @@ Também existe a chave `fiscal.iva.lei_22_2022` para a Lei n.º 22/2022, art. 17 ## Quatro ficheiros: origem e pendências por tipo -| Ficheiro e título | Diploma/conteúdo e origem | Autenticidade | Vigência/actualidade | Possibilidade de divulgação | -|---|---|---|---|---| -| `Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf` — Lei n.º 22/2022, alteração ao CIVA | [INM — 2.º Suplemento do BR n.º 250](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da); cópia também comparada byte a byte com o endpoint oficial AT | Confirmada ao nível da origem e bytes (1.834.284 bytes; hash no registo de migração) | Documento alterador de 2022, marcado `amended`; não é consolidação integral actual. Art. 17(1) conferido, mas período e alterações posteriores devem acompanhar a consulta | A lei é texto oficial. Falta decisão administrativa documentada sobre divulgação desta composição/fac-símile PDF e confirmação de direitos de reutilização do artefacto. O registo continua `internal`, direitos `unconfirmed` | -| `BR-248-I-Suplemento-1-2025.pdf` — BR n.º 248/2025, Suplemento n.º 1 | [INM — ficha da edição](https://inm.gov.mz/pt-br/content/suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie); PDF comparado byte a byte com WebDAV oficial AT | Confirmada ao nível da edição, origem oficial e bytes (440.018 bytes) | Edição contém seis diplomas distintos (Leis 7–12/2025). Não há um único estado de vigência para o boletim; revisão deve ser por lei/artigo e regulamento | Falta documentar a autorização aplicável à divulgação do ficheiro composto, incluindo a apresentação integral do boletim. Permanece privado com direitos `unconfirmed` | -| `BR-89-I-Serie-2017-06-08.pdf` — BR n.º 89/2017, aviso de rectificação da Lei n.º 13/2016 | [INM — ficha do BR 89, p. 569](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569) | A ficha oficial confirma a existência do aviso; o PDF local não foi comparado com exemplar oficial descarregado e a cadeia de custódia não está demonstrada | O aviso altera o CIVA e manda rectificar anexos I–III. Os anexos substitutivos e o efeito temporal ainda não foram cotejados com cópia autêntica; não serve como consolidação | Falta autenticar o binário e esclarecer a divulgação do fac-símile. Mantido privado; associação candidata pendente, não exibida como fundamento confirmado | -| `INE-Codificador-Divisao-Politico-Administrativa-2012.pdf` — Codificador DPA, edição de 07/06/2012 | [INE — PDF oficial](https://www.ine.gov.mz/documents/20119/44337/Codificador%20da%20Divisao%20Politico%20administrativa.pdf/14aed3d0-9296-c038-dbac-041a5cf40f1d) | Cópia local comparada byte a byte com ficheiro servido pelo INE | Edição de 2012; actualidade de códigos/designações territoriais precisa de confirmação antes de orientar cadastro actual | Falta documentar direitos de reutilização da composição PDF. Mantido privado. Não associado a requisito fiscal | +| Ficheiro e título | Diploma/conteúdo e origem | Autenticidade | Vigência/actualidade | Possibilidade de divulgação | +| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf` — Lei n.º 22/2022, alteração ao CIVA | [INM — 2.º Suplemento do BR n.º 250](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da); cópia também comparada byte a byte com o endpoint oficial AT | Confirmada ao nível da origem e bytes (1.834.284 bytes; hash no registo de migração) | Documento alterador de 2022, marcado `amended`; não é consolidação integral actual. Art. 17(1) conferido, mas período e alterações posteriores devem acompanhar a consulta | A lei é texto oficial. Falta decisão administrativa documentada sobre divulgação desta composição/fac-símile PDF e confirmação de direitos de reutilização do artefacto. O registo continua `internal`, direitos `unconfirmed` | +| `BR-248-I-Suplemento-1-2025.pdf` — BR n.º 248/2025, Suplemento n.º 1 | [INM — ficha da edição](https://inm.gov.mz/pt-br/content/suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie); PDF comparado byte a byte com WebDAV oficial AT | Confirmada ao nível da edição, origem oficial e bytes (440.018 bytes) | Edição contém seis diplomas distintos (Leis 7–12/2025). Não há um único estado de vigência para o boletim; revisão deve ser por lei/artigo e regulamento | Falta documentar a autorização aplicável à divulgação do ficheiro composto, incluindo a apresentação integral do boletim. Permanece privado com direitos `unconfirmed` | +| `BR-89-I-Serie-2017-06-08.pdf` — BR n.º 89/2017, aviso de rectificação da Lei n.º 13/2016 | [INM — ficha do BR 89, p. 569](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569) | A ficha oficial confirma a existência do aviso; o PDF local não foi comparado com exemplar oficial descarregado e a cadeia de custódia não está demonstrada | O aviso altera o CIVA e manda rectificar anexos I–III. Os anexos substitutivos e o efeito temporal ainda não foram cotejados com cópia autêntica; não serve como consolidação | Falta autenticar o binário e esclarecer a divulgação do fac-símile. Mantido privado; associação candidata pendente, não exibida como fundamento confirmado | +| `INE-Codificador-Divisao-Politico-Administrativa-2012.pdf` — Codificador DPA, edição de 07/06/2012 | [INE — PDF oficial](https://www.ine.gov.mz/documents/20119/44337/Codificador%20da%20Divisao%20Politico%20administrativa.pdf/14aed3d0-9296-c038-dbac-041a5cf40f1d) | Cópia local comparada byte a byte com ficheiro servido pelo INE | Edição de 2012; actualidade de códigos/designações territoriais precisa de confirmação antes de orientar cadastro actual | Falta documentar direitos de reutilização da composição PDF. Mantido privado. Não associado a requisito fiscal | Nenhum destes quatro ficheiros foi publicado nesta execução. O estado antimalware limpo não é autorização de divulgação. As pendências são por ficheiro e independem umas das outras. diff --git a/docs/investmoz/education/decisions.md b/docs/investmoz/education/decisions.md index 7ce09ab0..68f50737 100644 --- a/docs/investmoz/education/decisions.md +++ b/docs/investmoz/education/decisions.md @@ -4,17 +4,17 @@ Estado: **validado tecnicamente para desenvolvimento local**, por revisão do re ## Decisões adoptadas -| ID | Decisão e fundamento confirmado | Execução e limites | -| --- | --- | --- | -| D-EDU-01 / B02 | O catálogo público usa `opportunities.id` bigint; os domínios novos usam UUID. Preservar ambos, com uma associação explícita, sem converter IDs existentes. | `education_investment_proposals` tem UUID e referências a `labour_demands` e `opportunities`. Uma ficha inicial por procura; restrições únicas e bloqueio transaccional evitam duplicação. `education_opportunities` conserva a semântica de oferta calendarizada. | -| D-EDU-02 / B03 | Reutilizar sessão, email verificado e `manage-portal` para preparação interna. O registo público não concede acesso. | As rotas novas estão no grupo de gestão, atrás de `EducationEnabled`. Não criar perfis institucionais ou OAuth/OIDC sem mandatos/issuer reais. A revisão por outro gestor continua distinta de validação oficial. | -| D-EDU-03 / B06 | Vagas formativas não equivalem a trabalhadores disponíveis. Fontes desconhecidas não equivalem a zero. A versão revista da procura é requisito para preparar uma proposta. | Comparação existente preservada; proposta referencia a versão revista e vigente. Alteração de versão produz conflito na criação e aviso na consulta da ficha anterior. Nenhuma soma de vagas é apresentada como défice laboral. | -| D-EDU-04 / B01 | A preparação de uma resposta pode ocorrer antes da decisão institucional; a publicação exige evidência que ainda não foi fornecida. | Criar apenas rascunhos, com fonte, riscos e intervenção obrigatórios; nenhum montante, instituição, capacidade ou projecto é inventado. A gestão geral também rejeita publicação de fichas educativas. Não chamar este estado de lacuna validada. | -| D-EDU-05 / B04 | Reutilizar a identidade do catálogo e o fluxo de interesse/CRM existente. Não duplicar investidores, contactos ou leads. | Ficha aponta para a oportunidade canónica. O encaminhamento educativo externo ainda não é activado: `suitecrm:probe` devolveu `disabled`; sem CRM homologado não declarar sincronização concluída. | -| D-EDU-06 / B05 | Nesta etapa não recolher beneficiários, dados académicos individuais ou localizações pessoais. | Ficha usa necessidades agregadas; campos de texto devem conter fontes e responsáveis institucionais, não listas de pessoas. Auditoria partilhada não replica o texto das fontes, riscos ou planos. | -| D-EDU-07 / B07 | Correspondência por regras não é IA. Não escolher fornecedor nem enviar dados a um modelo sem configuração e critérios aprovados. | IA permanece por configurar; sem inferência externa, decisão automática ou alegação de precisão. | -| D-EDU-08 / B08 | Indicadores internos devem medir apenas o que os dados sustentam. | Contagens de declarações, procura por rever e fontes expiradas, rotuladas como globais e sem efeitos dos filtros. Sem metas fictícias, taxas de emprego ou agregação indevida de trabalhadores. | -| D-EDU-09 | Alteração aditiva e reversão sem perda. | Nova migration não altera tabelas existentes; rollback recusa apagar fichas existentes. Reversão operacional: desactivar `EDUCATION_ENABLED`, preservar as tabelas e reconciliar antes de qualquer restauro. | +| ID | Decisão e fundamento confirmado | Execução e limites | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| D-EDU-01 / B02 | O catálogo público usa `opportunities.id` bigint; os domínios novos usam UUID. Preservar ambos, com uma associação explícita, sem converter IDs existentes. | `education_investment_proposals` tem UUID e referências a `labour_demands` e `opportunities`. Uma ficha inicial por procura; restrições únicas e bloqueio transaccional evitam duplicação. `education_opportunities` conserva a semântica de oferta calendarizada. | +| D-EDU-02 / B03 | Reutilizar sessão, email verificado e `manage-portal` para preparação interna. O registo público não concede acesso. | As rotas novas estão no grupo de gestão, atrás de `EducationEnabled`. Não criar perfis institucionais ou OAuth/OIDC sem mandatos/issuer reais. A revisão por outro gestor continua distinta de validação oficial. | +| D-EDU-03 / B06 | Vagas formativas não equivalem a trabalhadores disponíveis. Fontes desconhecidas não equivalem a zero. A versão revista da procura é requisito para preparar uma proposta. | Comparação existente preservada; proposta referencia a versão revista e vigente. Alteração de versão produz conflito na criação e aviso na consulta da ficha anterior. Nenhuma soma de vagas é apresentada como défice laboral. | +| D-EDU-04 / B01 | A preparação de uma resposta pode ocorrer antes da decisão institucional; a publicação exige evidência que ainda não foi fornecida. | Criar apenas rascunhos, com fonte, riscos e intervenção obrigatórios; nenhum montante, instituição, capacidade ou projecto é inventado. A gestão geral também rejeita publicação de fichas educativas. Não chamar este estado de lacuna validada. | +| D-EDU-05 / B04 | Reutilizar a identidade do catálogo e o fluxo de interesse/CRM existente. Não duplicar investidores, contactos ou leads. | Ficha aponta para a oportunidade canónica. O encaminhamento educativo externo ainda não é activado: `suitecrm:probe` devolveu `disabled`; sem CRM homologado não declarar sincronização concluída. | +| D-EDU-06 / B05 | Nesta etapa não recolher beneficiários, dados académicos individuais ou localizações pessoais. | Ficha usa necessidades agregadas; campos de texto devem conter fontes e responsáveis institucionais, não listas de pessoas. Auditoria partilhada não replica o texto das fontes, riscos ou planos. | +| D-EDU-07 / B07 | Correspondência por regras não é IA. Não escolher fornecedor nem enviar dados a um modelo sem configuração e critérios aprovados. | IA permanece por configurar; sem inferência externa, decisão automática ou alegação de precisão. | +| D-EDU-08 / B08 | Indicadores internos devem medir apenas o que os dados sustentam. | Contagens de declarações, procura por rever e fontes expiradas, rotuladas como globais e sem efeitos dos filtros. Sem metas fictícias, taxas de emprego ou agregação indevida de trabalhadores. | +| D-EDU-09 | Alteração aditiva e reversão sem perda. | Nova migration não altera tabelas existentes; rollback recusa apagar fichas existentes. Reversão operacional: desactivar `EDUCATION_ENABLED`, preservar as tabelas e reconciliar antes de qualquer restauro. | ## Contratos preservados e análise de impacto diff --git a/docs/investmoz/education/implementation-status.md b/docs/investmoz/education/implementation-status.md index 130852ad..f45f867d 100644 --- a/docs/investmoz/education/implementation-status.md +++ b/docs/investmoz/education/implementation-status.md @@ -23,69 +23,69 @@ As rotas continuam internas, sob `/management/education`, com sessão, verifica “Parcial” significa código existente com limitações concretas; nenhum RF abaixo foi declarado integralmente cumprido. Os critérios completos mantêm-se em `requirements.csv`. -| RF-EDU | Estado funcional | Evidência e trabalho por concluir | -| --- | --- | --- | -| 001 | Parcial | Cadastro de código/nome; faltam taxonomia, sinónimos, níveis, fontes e revisão de versões. B01/B06. | -| 002 | Parcial | Procura por projecto, ocupação, competência principal, território e período; faltam múltiplas competências, contexto produtivo completo e mandatos. B01/B03. | -| 003 | Parcial | Instituições, cursos e capacidade declarada; faltam cobertura, documentação e vínculo institucional verificado. B01/B03. | -| 004 | Parcial | Calculador e correspondência determinística; faltam disponibilidade comprovada de trabalhadores e alocação sem dupla contagem. B06. | -| 005 | Bloqueado | Confirmar extensão do catálogo canónico; `education_opportunities` representa oferta calendarizada, não investimento educativo. B02. | -| 006 | Parcial | Navegação interna e análise por procura; falta navegação projecto/cluster/corredor com relações confirmadas. B01/B02. | -| 007 | Bloqueado | Não atribuir acreditação por estado de cadastro; faltam autoridade, evidência e mandato. B01/B03. | -| 008 | Bloqueado | Fluxo geral de interesse existe no portal; associação educativa depende de oportunidade canónica aprovada. B02/B05. | -| 009 | Parcial | Empregador declarado como texto; falta identidade institucional e distinção entre estimativa e compromisso. B01/B03. | -| 010 | Bloqueado | Plano formativo permanece proposta; exige lacuna validada e responsáveis. B01/B02/B06. | -| 011 | Parcial | Critérios apresentados: ocupação, competência, qualificação textual, território, validade e conclusão; faltam equivalências e revisão da correspondência. B06. | -| 012 | Fase 2 | Resultados educativos fora da execução actual; requerem tratamento autorizado. B05. | -| 013 | Fase 2 | Bolsas e campanhas permanecem no roteiro. B05. | -| 014 | Bloqueado | Sem serviço de IA Education activado; fornecedor/modelo, fontes e métricas pendentes. B07. | -| 015 | Parcial | Listagens paginadas; faltam pesquisa, filtros e painel reconciliado. B01/B06/B08. | -| 016 | Bloqueado | Rotas web não equivalem à API proposta; faltam identidade, contratos e eventos aprovados. B03. | -| 017 | Parcial | Cadastro mínimo de ocupações; faltam perfis, relações com competências e níveis validados. B06. | -| 018 | Parcial | Datas e território declarados; sem previsão, mobilidade ou ligação contextual completa. B01/B06. | -| 019 | Parcial | Vagas por curso/período; faltam recursos partilhados, capacidade instalada e utilização. B06. | -| 020 | Bloqueado | Revisão de declarações é interna; não existe ciclo institucional de validação, encerramento e reabertura da lacuna. B01/B03/B06. | -| 021 | Bloqueado | CRM geral reutilizável; vínculo educativo e sincronização dependem de B02/B04/B05. | -| 022 | Bloqueado | Necessidades de equipamento/instalações dependem da resposta formativa aprovada. B02/B06. | -| 023 | Bloqueado | Necessidades de formadores dependem da metodologia; dados individuais apenas na fase autorizada. B05/B06. | -| 024 | Bloqueado | Estimativas e sustentabilidade dependem de fontes e modelo de oportunidade aprovado. B02/B06. | -| 025 | Fase 2 | Parcerias no roteiro; responsabilidades e contratos por validar. | -| 026 | Fase 2 | Estágios e aprendizagem no roteiro; sem contratação automática. | -| 027 | Fase 2 | Turmas e beneficiários não recolhidos. B05. | -| 028 | Fases 2/3 | Conclusão e certificação externa dependem de fontes competentes; portal não certifica. | -| 029 | Fase 2 | Inserção e absorção exigem evidência autorizada e confirmação do empregador. B05. | -| 030 | Fase 2 | Retenção exige períodos, seguimento e base de tratamento definidos. B05. | -| 031 | Bloqueado | Referência territorial existente; mapa/relatórios Education requerem fontes geográficas e divulgação autorizadas. B01/B05/B06. | -| 032 | Parcial | Gate de gestão, revisão independente e auditoria partilhada; faltam mandatos e matriz de permissões institucionais. B03. | -| 033 | Parcial | Validação de datas, fontes, referências e concorrência; migração de fontes reais, reconciliação e retenção pendentes. B05/B06/B08. | +| RF-EDU | Estado funcional | Evidência e trabalho por concluir | +| ------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 001 | Parcial | Cadastro de código/nome; faltam taxonomia, sinónimos, níveis, fontes e revisão de versões. B01/B06. | +| 002 | Parcial | Procura por projecto, ocupação, competência principal, território e período; faltam múltiplas competências, contexto produtivo completo e mandatos. B01/B03. | +| 003 | Parcial | Instituições, cursos e capacidade declarada; faltam cobertura, documentação e vínculo institucional verificado. B01/B03. | +| 004 | Parcial | Calculador e correspondência determinística; faltam disponibilidade comprovada de trabalhadores e alocação sem dupla contagem. B06. | +| 005 | Bloqueado | Confirmar extensão do catálogo canónico; `education_opportunities` representa oferta calendarizada, não investimento educativo. B02. | +| 006 | Parcial | Navegação interna e análise por procura; falta navegação projecto/cluster/corredor com relações confirmadas. B01/B02. | +| 007 | Bloqueado | Não atribuir acreditação por estado de cadastro; faltam autoridade, evidência e mandato. B01/B03. | +| 008 | Bloqueado | Fluxo geral de interesse existe no portal; associação educativa depende de oportunidade canónica aprovada. B02/B05. | +| 009 | Parcial | Empregador declarado como texto; falta identidade institucional e distinção entre estimativa e compromisso. B01/B03. | +| 010 | Bloqueado | Plano formativo permanece proposta; exige lacuna validada e responsáveis. B01/B02/B06. | +| 011 | Parcial | Critérios apresentados: ocupação, competência, qualificação textual, território, validade e conclusão; faltam equivalências e revisão da correspondência. B06. | +| 012 | Fase 2 | Resultados educativos fora da execução actual; requerem tratamento autorizado. B05. | +| 013 | Fase 2 | Bolsas e campanhas permanecem no roteiro. B05. | +| 014 | Bloqueado | Sem serviço de IA Education activado; fornecedor/modelo, fontes e métricas pendentes. B07. | +| 015 | Parcial | Listagens paginadas; faltam pesquisa, filtros e painel reconciliado. B01/B06/B08. | +| 016 | Bloqueado | Rotas web não equivalem à API proposta; faltam identidade, contratos e eventos aprovados. B03. | +| 017 | Parcial | Cadastro mínimo de ocupações; faltam perfis, relações com competências e níveis validados. B06. | +| 018 | Parcial | Datas e território declarados; sem previsão, mobilidade ou ligação contextual completa. B01/B06. | +| 019 | Parcial | Vagas por curso/período; faltam recursos partilhados, capacidade instalada e utilização. B06. | +| 020 | Bloqueado | Revisão de declarações é interna; não existe ciclo institucional de validação, encerramento e reabertura da lacuna. B01/B03/B06. | +| 021 | Bloqueado | CRM geral reutilizável; vínculo educativo e sincronização dependem de B02/B04/B05. | +| 022 | Bloqueado | Necessidades de equipamento/instalações dependem da resposta formativa aprovada. B02/B06. | +| 023 | Bloqueado | Necessidades de formadores dependem da metodologia; dados individuais apenas na fase autorizada. B05/B06. | +| 024 | Bloqueado | Estimativas e sustentabilidade dependem de fontes e modelo de oportunidade aprovado. B02/B06. | +| 025 | Fase 2 | Parcerias no roteiro; responsabilidades e contratos por validar. | +| 026 | Fase 2 | Estágios e aprendizagem no roteiro; sem contratação automática. | +| 027 | Fase 2 | Turmas e beneficiários não recolhidos. B05. | +| 028 | Fases 2/3 | Conclusão e certificação externa dependem de fontes competentes; portal não certifica. | +| 029 | Fase 2 | Inserção e absorção exigem evidência autorizada e confirmação do empregador. B05. | +| 030 | Fase 2 | Retenção exige períodos, seguimento e base de tratamento definidos. B05. | +| 031 | Bloqueado | Referência territorial existente; mapa/relatórios Education requerem fontes geográficas e divulgação autorizadas. B01/B05/B06. | +| 032 | Parcial | Gate de gestão, revisão independente e auditoria partilhada; faltam mandatos e matriz de permissões institucionais. B03. | +| 033 | Parcial | Validação de datas, fontes, referências e concorrência; migração de fontes reais, reconciliação e retenção pendentes. B05/B06/B08. | ## 3. Validação local Ambiente de teste novo e isolado: PostgreSQL/PostGIS local, porta 55440, base `investmoz_education_test`; PHP 8.5.11 com driver PostgreSQL. Não foi usada a base da aplicação para migrations ou testes. Não foram inseridos dados institucionais reais. -| Verificação | Evidência | -| --- | --- | -| Migrations | Aplicadas com sucesso na base temporária inicialmente vazia. | -| PHPUnit | 107 testes, 884 verificações, todos aprovados. Inclui 9 testes Education. | -| Exemplos e consistência documental | 11 testes Node aprovados; não equivalem a conformidade funcional integral. | -| TypeScript | `tsc --noEmit` aprovado. | -| Build | `npm run build` aprovado. | -| Análise estática | PHPStan nível 7, zero erros nos seis ficheiros Education/middleware analisados. | -| Formatação PHP | Pint aprovado nos cinco ficheiros PHP alterados. | -| Confirmação final Education | 9 testes, 136 verificações aprovadas após a última correcção do controller. | +| Verificação | Evidência | +| ---------------------------------- | ------------------------------------------------------------------------------- | +| Migrations | Aplicadas com sucesso na base temporária inicialmente vazia. | +| PHPUnit | 107 testes, 884 verificações, todos aprovados. Inclui 9 testes Education. | +| Exemplos e consistência documental | 11 testes Node aprovados; não equivalem a conformidade funcional integral. | +| TypeScript | `tsc --noEmit` aprovado. | +| Build | `npm run build` aprovado. | +| Análise estática | PHPStan nível 7, zero erros nos seis ficheiros Education/middleware analisados. | +| Formatação PHP | Pint aprovado nos cinco ficheiros PHP alterados. | +| Confirmação final Education | 9 testes, 136 verificações aprovadas após a última correcção do controller. | Os testes Education usam transacções revertidas e verificam que o destino termina em `_test`. A suite preexistente usa também recriação do esquema, exclusivamente na base descartável. Não foram efectuados ensaios de carga, auditoria independente de segurança, validação visual em navegador, testes end-to-end ou homologação institucional. Não transferir a aprovação destes testes para esses controlos. ## 4. Decisões necessárias para concluir o MVP -| Decisão concreta | Proposta para revisão | Responsável | Condição de avanço | -| --- | --- | --- | --- | -| Identidade da oportunidade educativa | Estender o domínio canónico de oportunidades, mantendo a oferta calendarizada separada; preservar IDs e contratos existentes. | Produto e Arquitectura | Aprovar mapeamento de identidade, campos, vínculo CRM e compatibilidade. B02. | -| Autoridade e permissões | Gestão interna não concede mandato institucional. Vincular validação a organização, âmbito e validade do mandato; manter autor diferente do revisor. | Segurança e entidades competentes | Aprovar matriz e comprovar OAuth/OIDC antes de API institucional. B03. | -| Dados e metodologia | Comparar pessoas disponíveis com procura; capacidade formativa é uma resposta potencial, sujeita a calendário, conclusão e recursos partilhados. | Secretariado e entidades competentes | Validar fontes, taxonomia, unidade, cobertura, actualidade e regra de alocação. B01/B06. | -| Piloto | Até três clusters e até três corredores, seleccionados pela matriz da especificação. Nenhum seleccionado artificialmente. | Patrocinador e Produto | Identificar projectos, instituições, responsáveis e fontes autorizadas. B01. | -| CRM e IA | Reutilizar integração existente após contratos; IA apenas recomendação revista, sem estados oficiais. | Responsável CRM e Comité de IA e Dados | Homologar sincronização, fornecedor/modelo e métricas. B04/B07. | -| Operação e dados pessoais | Manter beneficiários fora desta implementação; definir retenção, divulgação, suporte, backup e restauro. | Dados, Segurança e Operações | Resolver B05/B08 e executar gates da especificação. | +| Decisão concreta | Proposta para revisão | Responsável | Condição de avanço | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- | +| Identidade da oportunidade educativa | Estender o domínio canónico de oportunidades, mantendo a oferta calendarizada separada; preservar IDs e contratos existentes. | Produto e Arquitectura | Aprovar mapeamento de identidade, campos, vínculo CRM e compatibilidade. B02. | +| Autoridade e permissões | Gestão interna não concede mandato institucional. Vincular validação a organização, âmbito e validade do mandato; manter autor diferente do revisor. | Segurança e entidades competentes | Aprovar matriz e comprovar OAuth/OIDC antes de API institucional. B03. | +| Dados e metodologia | Comparar pessoas disponíveis com procura; capacidade formativa é uma resposta potencial, sujeita a calendário, conclusão e recursos partilhados. | Secretariado e entidades competentes | Validar fontes, taxonomia, unidade, cobertura, actualidade e regra de alocação. B01/B06. | +| Piloto | Até três clusters e até três corredores, seleccionados pela matriz da especificação. Nenhum seleccionado artificialmente. | Patrocinador e Produto | Identificar projectos, instituições, responsáveis e fontes autorizadas. B01. | +| CRM e IA | Reutilizar integração existente após contratos; IA apenas recomendação revista, sem estados oficiais. | Responsável CRM e Comité de IA e Dados | Homologar sincronização, fornecedor/modelo e métricas. B04/B07. | +| Operação e dados pessoais | Manter beneficiários fora desta implementação; definir retenção, divulgação, suporte, backup e restauro. | Dados, Segurança e Operações | Resolver B05/B08 e executar gates da especificação. | O texto de origem exige: «Quando faltar informação do repositório ou da arquitectura existente, produz uma especificação, interface ou pseudocódigo marcado como Pendente de validação, e não código executável apresentado como definitivo». Exige ainda que conflitos sejam assinalados como Bloqueio. Por isso, este diagnóstico não cria tabelas, estados oficiais, integrações ou identidades para contornar as decisões acima. diff --git a/docs/investmoz/education/skills-education-schema.md b/docs/investmoz/education/skills-education-schema.md index c866e338..eddfb34d 100644 --- a/docs/investmoz/education/skills-education-schema.md +++ b/docs/investmoz/education/skills-education-schema.md @@ -15,32 +15,32 @@ Executado o anexo de tabelas recebido em 30/09/2026, adaptado aos mestres existe As seguintes tabelas são extensões com **PK também FK obrigatória** para o mestre. Não geram um segundo identificador da entidade. Nomes/códigos e outros campos indicados continuam a ser lidos no mestre; não foram copiados. -| Extensão em skills_edu | Mestre em public | Informação mantida no mestre | -|---|---|---| -| skill_details | skills | UUID, código e nome PT | -| occupation_details | occupations | UUID, código e nome PT | -| program_details | courses | UUID, código, título PT, instituição/provider e estado | -| human_resource_need_details | labour_demands | UUID, projecto, localização, ocupação, quantidade, qualificação, período, fonte e estado | +| Extensão em skills_edu | Mestre em public | Informação mantida no mestre | +| ------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------ | +| skill_details | skills | UUID, código e nome PT | +| occupation_details | occupations | UUID, código e nome PT | +| program_details | courses | UUID, código, título PT, instituição/provider e estado | +| human_resource_need_details | labour_demands | UUID, projecto, localização, ocupação, quantidade, qualificação, período, fonte e estado | | educational_opportunity_details | education_investment_proposals | UUID da proposta; título, descrição, valor e moeda continuam na oportunidade ligada por opportunity_id | -| qualification_document_details | documents | UUID, nome do ficheiro, MIME, storage_key e SHA-256 | -| consent_details | consents | UUID, finalidade, estado, concessão e retirada | +| qualification_document_details | documents | UUID, nome do ficheiro, MIME, storage_key e SHA-256 | +| consent_details | consents | UUID, finalidade, estado, concessão e retirada | Os estados destas entidades seguem o mestre existente: por exemplo, consentimento retirado usa `withdrawn`, não uma segunda coluna `revoked`. A unicidade de códigos de competências/ocupações/cursos conserva as regras actuais dos mestres, incluindo soft delete. A versão do grupo documental na extensão refere-se ao comprovativo; `documents.version_no` continua a ser a versão do registo mestre. O consentimento deve ter `public.consents.subject_ref` igual ao UUID do perfil. O trigger da extensão valida esta ligação e que a expiração é posterior à concessão. Não converte consentimento de outra finalidade nem muda o mestre. -| Referência do anexo | Mestre e tipo confirmados | -|---|---| -| user_master_id e campos de actores *_by/*_user_id | public.users.id — bigint | -| sector_master_id | public.sectors.id — bigint | -| company_master_id / organisation_master_id | public.organizations.id — UUID | -| institution_master_id | public.training_providers.id — UUID | -| program_master_id | public.courses.id — UUID | -| project_master_id | public.projects.id — UUID | -| cluster_master_id | public.economic_clusters.id — UUID | -| corridor_master_id | public.development_corridors.id — UUID | -| zee_master_id / zfi_master_id | public.investment_zones.id — UUID | -| location_master_id | public.geo_locations.id — UUID | +| Referência do anexo | Mestre e tipo confirmados | +| -------------------------------------------------- | -------------------------------------- | +| user_master_id e campos de actores _\_by/__user_id | public.users.id — bigint | +| sector_master_id | public.sectors.id — bigint | +| company_master_id / organisation_master_id | public.organizations.id — UUID | +| institution_master_id | public.training_providers.id — UUID | +| program_master_id | public.courses.id — UUID | +| project_master_id | public.projects.id — UUID | +| cluster_master_id | public.economic_clusters.id — UUID | +| corridor_master_id | public.development_corridors.id — UUID | +| zee_master_id / zfi_master_id | public.investment_zones.id — UUID | +| location_master_id | public.geo_locations.id — UUID | As FK novas para mestres usam DELETE RESTRICT. Relações internas mantêm CASCADE/SET NULL/RESTRICT definidos no anexo. Não há conversão bigint–UUID. A escolha ZEE versus ZFI exige ainda validação de categoria na futura aplicação: a FK confirma a existência da zona, não a sua categoria. diff --git a/docs/investmoz/education/skills-functional-diagnosis.md b/docs/investmoz/education/skills-functional-diagnosis.md index f2573e1c..8eb76f6c 100644 --- a/docs/investmoz/education/skills-functional-diagnosis.md +++ b/docs/investmoz/education/skills-functional-diagnosis.md @@ -20,30 +20,30 @@ A autorização do utilizador para executar está registada na conversa. Não se ## Conservar–Melhorar–Substituir–Descontinuar -| Decisão | Componentes | Justificação | -|---|---|---| -| Conservar | users, organizations, sectors, projects, economic_clusters, development_corridors, investment_zones, geo_locations | Identidades mestre; não converter bigint em UUID nem duplicar taxonomias. | -| Conservar | skills, occupations, courses, training_providers, consents, documents e extensões skills_edu | Reutilizar PK/FK e estados do mestre; novos dados por migrations aditivas. | -| Conservar | Fluxos internos, revisão independente, cálculo de lacunas e oportunidade canónica | Compatibilidade; capacidade de formação não equivale a oferta laboral. | -| Melhorar | labour_demands e serviços de procura | Rascunhos, arquivo, várias competências, contexto, versões e autorização por organização. | -| Melhorar | Interfaces e pesquisa | Painéis por actor, filtros combinados, PT/EN, acessibilidade e precisão territorial autorizada. | -| Melhorar | Consentimento, auditoria e documentos | Finalidades separadas, revogação, scanner real, quarentena e autorização em cada leitura. | -| Substituir | Nenhum componente nesta fase | Não há evidência que justifique reconstrução integral. | -| Descontinuar | Nenhum contrato nesta fase | Preservar consumidores e dados existentes. | +| Decisão | Componentes | Justificação | +| ------------ | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| Conservar | users, organizations, sectors, projects, economic_clusters, development_corridors, investment_zones, geo_locations | Identidades mestre; não converter bigint em UUID nem duplicar taxonomias. | +| Conservar | skills, occupations, courses, training_providers, consents, documents e extensões skills_edu | Reutilizar PK/FK e estados do mestre; novos dados por migrations aditivas. | +| Conservar | Fluxos internos, revisão independente, cálculo de lacunas e oportunidade canónica | Compatibilidade; capacidade de formação não equivale a oferta laboral. | +| Melhorar | labour_demands e serviços de procura | Rascunhos, arquivo, várias competências, contexto, versões e autorização por organização. | +| Melhorar | Interfaces e pesquisa | Painéis por actor, filtros combinados, PT/EN, acessibilidade e precisão territorial autorizada. | +| Melhorar | Consentimento, auditoria e documentos | Finalidades separadas, revogação, scanner real, quarentena e autorização em cada leitura. | +| Substituir | Nenhum componente nesta fase | Não há evidência que justifique reconstrução integral. | +| Descontinuar | Nenhum contrato nesta fase | Preservar consumidores e dados existentes. | ## Plano por ficheiro — proposto, ainda não executado -| Ordem | Caminhos | Alteração e verificação necessária | -|---|---|---| -| 1 | `docs/investmoz/education/requirements.csv`, nova matriz funcional | Relacionar RF-CAN, CA/CT, RF-EDU/CRM/EII/GEO e A.31 com entidades, APIs, ecrãs, evidências e responsáveis. Não atribuir IDs normativos sem conferir a fonte. | -| 2 | `config/education.php`, `app/Http/Middleware/EducationEnabled.php` | Flags independentes; desligar sem apagar dados. Testar acesso negado e contratos actuais. | -| 3 | Nova migration Laravel posterior a `100600` | Evoluir rascunho/arquivo, concorrência e escopos conforme desenho aprovado; não reescrever SQL aplicado. Testar ida/volta em base isolada e recusa de perda de dados. | -| 4 | Novos serviços em `app/Services/Education/`, requests e policies | Escritas transaccionais, segregação, organização vigente, validação contextual e auditoria sem conteúdo pessoal. | -| 5 | Novos controllers e rotas versionadas | Perfis, necessidades, instituições, consentimentos e documentos; paginação, limites, conflitos de versão e respostas sem enumeração. Manter rotas internas. | -| 6 | `resources/js/pages/education/` e componentes partilhados | Formulários por actor, rascunho/retoma, confirmação, revisão e estados; PT/EN, teclado e móvel. | -| 7 | Serviços de pesquisa/correspondência e jobs | Autorização antes de resultados; condições aprovadas antes da pontuação; versão e explicação; revogação verificada também na execução de jobs. Sem encaminhamento automático. | -| 8 | `app/Integrations/SuiteCrm/`, contratos e reconciliação | Só campos e finalidades aprovados; deduplicação, idempotência e recuperação; alternativa manual enquanto contrato externo pendente. | -| 9 | `tests/Feature/`, `tests/Unit/`, ferramentas de navegador e contratos | CT-01–CT-16: fluxos, fronteiras, acesso, documentos maliciosos, revogação, concorrência, PT/EN, recuperação e carga com linha de base real. | +| Ordem | Caminhos | Alteração e verificação necessária | +| ----- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | `docs/investmoz/education/requirements.csv`, nova matriz funcional | Relacionar RF-CAN, CA/CT, RF-EDU/CRM/EII/GEO e A.31 com entidades, APIs, ecrãs, evidências e responsáveis. Não atribuir IDs normativos sem conferir a fonte. | +| 2 | `config/education.php`, `app/Http/Middleware/EducationEnabled.php` | Flags independentes; desligar sem apagar dados. Testar acesso negado e contratos actuais. | +| 3 | Nova migration Laravel posterior a `100600` | Evoluir rascunho/arquivo, concorrência e escopos conforme desenho aprovado; não reescrever SQL aplicado. Testar ida/volta em base isolada e recusa de perda de dados. | +| 4 | Novos serviços em `app/Services/Education/`, requests e policies | Escritas transaccionais, segregação, organização vigente, validação contextual e auditoria sem conteúdo pessoal. | +| 5 | Novos controllers e rotas versionadas | Perfis, necessidades, instituições, consentimentos e documentos; paginação, limites, conflitos de versão e respostas sem enumeração. Manter rotas internas. | +| 6 | `resources/js/pages/education/` e componentes partilhados | Formulários por actor, rascunho/retoma, confirmação, revisão e estados; PT/EN, teclado e móvel. | +| 7 | Serviços de pesquisa/correspondência e jobs | Autorização antes de resultados; condições aprovadas antes da pontuação; versão e explicação; revogação verificada também na execução de jobs. Sem encaminhamento automático. | +| 8 | `app/Integrations/SuiteCrm/`, contratos e reconciliação | Só campos e finalidades aprovados; deduplicação, idempotência e recuperação; alternativa manual enquanto contrato externo pendente. | +| 9 | `tests/Feature/`, `tests/Unit/`, ferramentas de navegador e contratos | CT-01–CT-16: fluxos, fronteiras, acesso, documentos maliciosos, revogação, concorrência, PT/EN, recuperação e carga com linha de base real. | ## Regras de desenho propostas para revisão @@ -57,15 +57,15 @@ Sem mudança da ligação principal de dados nesta etapa. Antes de futuras migra **Não concluído. Não iniciar desenvolvimento funcional sob este prompt até resolver os bloqueadores materiais expressamente definidos nele.** -| Elemento | Evidência disponível / falta concreta | -|---|---| -| Autorização para executar | Recebida; não precisa de repetição. | -| Rastreabilidade | Há `requirements.csv`, `requirements-audit.csv` e especificação anterior. Há referências a A.31, mas não foi identificada nesta pesquisa a secção normativa integral com versão e correspondência ao novo anexo. | -| Âmbito | O novo pedido inclui indivíduos, instituições e correspondência; a decisão antiga D-EDU-06 excluía dados individuais. Actualizar formalmente o registo de âmbito com o novo pedido, sem tratar a autorização de desenvolvimento como consentimento dos candidatos. | -| Responsabilidade e tratamento | Faltam identificação do responsável funcional e política aplicável de finalidade, retenção, menores e destinatários. Papéis genéricos não são designação de pessoas/entidades. | -| Correspondência | Faltam regras, pesos e conjunto de referência aprovados; não adoptar exemplos fictícios como dados operacionais. | -| Desempenho | Falta linha de base da Fase Zero; não criar limites arbitrários para declarar CT-14 aprovado. | -| Integração | Falta contrato específico de candidatos/consentimento com CRM e confirmação da aplicabilidade de AsyncAPI. | -| Aceite | UAT com quatro perfis e decisões dos responsáveis não foram realizados; nenhuma aprovação fabricada. | +| Elemento | Evidência disponível / falta concreta | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Autorização para executar | Recebida; não precisa de repetição. | +| Rastreabilidade | Há `requirements.csv`, `requirements-audit.csv` e especificação anterior. Há referências a A.31, mas não foi identificada nesta pesquisa a secção normativa integral com versão e correspondência ao novo anexo. | +| Âmbito | O novo pedido inclui indivíduos, instituições e correspondência; a decisão antiga D-EDU-06 excluía dados individuais. Actualizar formalmente o registo de âmbito com o novo pedido, sem tratar a autorização de desenvolvimento como consentimento dos candidatos. | +| Responsabilidade e tratamento | Faltam identificação do responsável funcional e política aplicável de finalidade, retenção, menores e destinatários. Papéis genéricos não são designação de pessoas/entidades. | +| Correspondência | Faltam regras, pesos e conjunto de referência aprovados; não adoptar exemplos fictícios como dados operacionais. | +| Desempenho | Falta linha de base da Fase Zero; não criar limites arbitrários para declarar CT-14 aprovado. | +| Integração | Falta contrato específico de candidatos/consentimento com CRM e confirmação da aplicabilidade de AsyncAPI. | +| Aceite | UAT com quatro perfis e decisões dos responsáveis não foram realizados; nenhuma aprovação fabricada. | Próxima entrada necessária: localização da versão normativa de A.31 e do pacote de decisões da Fase Zero, incluindo os responsáveis e políticas acima. Se esses documentos ainda não existirem, é necessário resolver a divergência entre a ordem de implementar e a proibição explícita de iniciar desenvolvimento contida neste anexo. diff --git a/docs/investmoz/existing-business/README.md b/docs/investmoz/existing-business/README.md index 0322eb66..dd08543a 100644 --- a/docs/investmoz/existing-business/README.md +++ b/docs/investmoz/existing-business/README.md @@ -14,20 +14,20 @@ Fase executada: descoberta e enquadramento. Todas as etapas estão autorizadas p ## Diagnóstico confirmado -| Componente | Evidência no repositório | Aplicação ao mercado | -| --- | --- | --- | -| Stack | composer.lock: Laravel 13.30.1, Fortify 1.39.0, Inertia Laravel 3.3.3; instalados React 19.2.8, TypeScript 5.9.3, Vite 8.2.2 | Compatível; sem nova dependência | -| Runtime e BD | Inventário apenas de leitura: PHP 8.5.11, PostgreSQL 17.11, PostGIS 3.6.2 | Reutilizar; desenvolvimento local não comprova prontidão de produção | -| Identidade | users, organizations, memberships; Fortify e manage-portal | Identidade da conta não comprova titularidade nem representação; falta mandato por anúncio | -| Catálogo | InvestmentCatalogue, investment_items e mapeamento de oportunidades | Preservar IDs; não converter oportunidade em negócio sem revisão | -| Workspace | InvestmentWorkspace, InvestmentSubmissionController | Cifragem, snapshots, consentimento, mensagens e notificações reutilizáveis; sem workflow MNE | -| Documentos | documents, anexos privados, download autenticado com checksum | Não equivale a data room: faltam grants temporais, NDA validado, watermark e scanner comprovado | -| CRM interno | ConvertInterestToLead, CrmTask, leads, interest_requests | Conversão transaccional, consentimento e próxima acção; falta deduplicação negócio/modalidade/período | -| CRM externo | SuiteCrmClient, config/suitecrm.php, testes com doubles | Timeout 10s, ligação 3s, retries de leitura 2 e circuito configurados; nenhuma sincronização MNE real comprovada | -| Territórios | Clusters/corredores/projectos, zonas, PostGIS | Reutilizar relações; GIS transversal ainda incompleto, conforme ../gis/README.md | -| Notificações | investment_notifications, outbox_events/inbox_events | Notificações internas e infraestrutura; não presumir envio externo ou SLA contratado | -| Pagamentos/facturação | Nenhum módulo operacional identificado nas áreas inspeccionadas | Fora do piloto; sem taxas, comissões, impostos ou cobrança inventados | -| Jurídico | Fontes de zonas e documentos; não foi encontrado motor de elegibilidade MNE | Não reutilizar validação de ZEE como análise jurídica de venda/cessão | +| Componente | Evidência no repositório | Aplicação ao mercado | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| Stack | composer.lock: Laravel 13.30.1, Fortify 1.39.0, Inertia Laravel 3.3.3; instalados React 19.2.8, TypeScript 5.9.3, Vite 8.2.2 | Compatível; sem nova dependência | +| Runtime e BD | Inventário apenas de leitura: PHP 8.5.11, PostgreSQL 17.11, PostGIS 3.6.2 | Reutilizar; desenvolvimento local não comprova prontidão de produção | +| Identidade | users, organizations, memberships; Fortify e manage-portal | Identidade da conta não comprova titularidade nem representação; falta mandato por anúncio | +| Catálogo | InvestmentCatalogue, investment_items e mapeamento de oportunidades | Preservar IDs; não converter oportunidade em negócio sem revisão | +| Workspace | InvestmentWorkspace, InvestmentSubmissionController | Cifragem, snapshots, consentimento, mensagens e notificações reutilizáveis; sem workflow MNE | +| Documentos | documents, anexos privados, download autenticado com checksum | Não equivale a data room: faltam grants temporais, NDA validado, watermark e scanner comprovado | +| CRM interno | ConvertInterestToLead, CrmTask, leads, interest_requests | Conversão transaccional, consentimento e próxima acção; falta deduplicação negócio/modalidade/período | +| CRM externo | SuiteCrmClient, config/suitecrm.php, testes com doubles | Timeout 10s, ligação 3s, retries de leitura 2 e circuito configurados; nenhuma sincronização MNE real comprovada | +| Territórios | Clusters/corredores/projectos, zonas, PostGIS | Reutilizar relações; GIS transversal ainda incompleto, conforme ../gis/README.md | +| Notificações | investment_notifications, outbox_events/inbox_events | Notificações internas e infraestrutura; não presumir envio externo ou SLA contratado | +| Pagamentos/facturação | Nenhum módulo operacional identificado nas áreas inspeccionadas | Fora do piloto; sem taxas, comissões, impostos ou cobrança inventados | +| Jurídico | Fontes de zonas e documentos; não foi encontrado motor de elegibilidade MNE | Não reutilizar validação de ZEE como análise jurídica de venda/cessão | Inventário agregado local em 28/09/2026: 103 tabelas, 38 migrations, 6 oportunidades, 6 investment_items, 16 projectos, 29 dossiers, 8 clusters, 6 corredores. Zero documentos, organizações, geometrias, localizações, zonas, pedidos de interesse e leads. Há 1 investidor; não foi lido o seu conteúdo. As 6 oportunidades não têm opportunity_profiles com source_reference; este indicador não prova ausência de todas as fontes possíveis. Nenhum destes registos foi reclassificado como negócio existente. @@ -35,14 +35,14 @@ Não se encontrou cadastro dedicado de anúncios MNE, mandatos, titularidades, l ## Conservar–Melhorar–Substituir–Descontinuar -| Decisão | Componente | Acção proposta | -| --- | --- | --- | -| Conservar | Laravel/React/PostGIS, identidade, documentos canónicos, outbox e IDs mestres | Adaptadores versionados; manter contratos existentes | -| Melhorar | Autorização | Função e atributo por anúncio, mandatário, revisor e aprovador; MFA privilegiado; segregação em dois níveis | -| Melhorar | Documentos e CRM | Quarentena/scanner, grants revogáveis, escopo por anúncio; idempotência e reconciliação MNE | -| Melhorar | Catálogo, pesquisa e GIS | Projecção pública mínima separada; indexar só atributos autorizados; testar reidentificação | -| Substituir, apenas no novo fluxo | Autodeclaração como prova de legitimidade | Evidência documental e revisão humana; preservar declarações históricas | -| Descontinuar | Nenhum componente existente nesta fase | Nenhuma remoção; retirar práticas de divulgação não autorizada apenas após análise de impacto | +| Decisão | Componente | Acção proposta | +| -------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| Conservar | Laravel/React/PostGIS, identidade, documentos canónicos, outbox e IDs mestres | Adaptadores versionados; manter contratos existentes | +| Melhorar | Autorização | Função e atributo por anúncio, mandatário, revisor e aprovador; MFA privilegiado; segregação em dois níveis | +| Melhorar | Documentos e CRM | Quarentena/scanner, grants revogáveis, escopo por anúncio; idempotência e reconciliação MNE | +| Melhorar | Catálogo, pesquisa e GIS | Projecção pública mínima separada; indexar só atributos autorizados; testar reidentificação | +| Substituir, apenas no novo fluxo | Autodeclaração como prova de legitimidade | Evidência documental e revisão humana; preservar declarações históricas | +| Descontinuar | Nenhum componente existente nesta fase | Nenhuma remoção; retirar práticas de divulgação não autorizada apenas após análise de impacto | ## Jornada proposta @@ -59,16 +59,16 @@ Estados solicitados: rascunho, submetido, em triagem, em revisão, pendente de i ## RACI proposto — atribuições nominais pendentes -| Actividade | Responsável pela execução (R) | Responsável final (A) | Consultados (C) | Informados (I) | -| --- | --- | --- | --- | --- | -| Escopo, piloto e política | Produto | Dono do Produto | Jurídico, Dados, Segurança | Operações | -| Modelo e contratos | Engenharia/Arquitectura | Arquitectura | Dados, CRM, Segurança | Produto | -| Legitimidade, direitos, NDA, KYC/AML aplicável | Jurídico/Conformidade | Responsável jurídico designado | Titular, entidade competente quando autorizada | Produto | -| Triagem e revisão documental | Operações/revisor | Gestor de validação | Jurídico, Dados | Anunciante | -| Aprovação/publicação | Aprovadores de níveis 1 e 2 | Responsável editorial designado | Jurídico, Segurança | CRM | -| Sala segura e incidentes | Segurança/Operações | Responsável de segurança | Jurídico, Dados | Produto | -| UAT e recuperação | QA/Operações | Dono do Produto | Arquitectura, Segurança | Suporte | -| Comissões/facturação futura | Finanças | Responsável financeiro | Jurídico, Auditoria | Produto | +| Actividade | Responsável pela execução (R) | Responsável final (A) | Consultados (C) | Informados (I) | +| ---------------------------------------------- | ----------------------------- | ------------------------------- | ---------------------------------------------- | -------------- | +| Escopo, piloto e política | Produto | Dono do Produto | Jurídico, Dados, Segurança | Operações | +| Modelo e contratos | Engenharia/Arquitectura | Arquitectura | Dados, CRM, Segurança | Produto | +| Legitimidade, direitos, NDA, KYC/AML aplicável | Jurídico/Conformidade | Responsável jurídico designado | Titular, entidade competente quando autorizada | Produto | +| Triagem e revisão documental | Operações/revisor | Gestor de validação | Jurídico, Dados | Anunciante | +| Aprovação/publicação | Aprovadores de níveis 1 e 2 | Responsável editorial designado | Jurídico, Segurança | CRM | +| Sala segura e incidentes | Segurança/Operações | Responsável de segurança | Jurídico, Dados | Produto | +| UAT e recuperação | QA/Operações | Dono do Produto | Arquitectura, Segurança | Suporte | +| Comissões/facturação futura | Finanças | Responsável financeiro | Jurídico, Auditoria | Produto | Papéis propostos não concedem permissões. Ninguém foi nomeado por inferência. Autor da submissão não pode aprovar a própria publicação; os dois níveis exigem segregação a especificar e testar. @@ -76,39 +76,39 @@ Papéis propostos não concedem permissões. Ninguém foi nomeado por inferênci Esta matriz é de **questões para validação**, não parecer ou afirmação de admissibilidade legal. Não foram inventadas leis, termos de uso ou autoridades competentes. Regra, fonte oficial, versão, entidade, parecer e validade ficam pendentes. -| Grupo de modalidades solicitadas | Objecto a distinguir | Verificação e documento necessário | Responsável proposto / estado | -| --- | --- | --- | --- | -| Venda 100%, parcial, aumento de capital, investidor financeiro, sócio estratégico, comprador | Empresa, participação, mandato e avaliação | Titularidade/representação, encargos, consentimentos e requisitos da operação | Jurídico + titular / pendente | -| Cessão de posição, franchising, joint venture | Contrato, licença de uso, posição contratual | Restrições, consentimentos de terceiros, validade e análise da cessão | Jurídico / pendente | -| Venda/arrendamento de activos, transferência de unidade, sucessão | Activo físico, unidade, contratos e obrigações | Cadeia documental, legitimidade, passivos e autorizações | Jurídico + diligência especializada / pendente | -| Recuperação, expansão, internacionalização | Pedido de capital e finalidade | Informação financeira rotulada, riscos e verificação independente | Revisor + assessor / pendente | -| Imobiliário, propriedades, benfeitorias, investimentos agrícolas | Imóvel/unidade, investimento, direito de uso/ocupação e contrato separados | Área/limites, ocupação, encargos, litígios, finalidade, prazo, autorizações e transmissibilidade | Jurídico fundiário + entidade competente por confirmar / pendente | -| Licenças mineiras/florestais e outras | Licença, titular declarado, entidade emissora e actividade | Fonte competente autorizada, validade, restrições, obrigações e possibilidade de transmissão | Jurídico sectorial + entidade por confirmar / pendente | +| Grupo de modalidades solicitadas | Objecto a distinguir | Verificação e documento necessário | Responsável proposto / estado | +| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | +| Venda 100%, parcial, aumento de capital, investidor financeiro, sócio estratégico, comprador | Empresa, participação, mandato e avaliação | Titularidade/representação, encargos, consentimentos e requisitos da operação | Jurídico + titular / pendente | +| Cessão de posição, franchising, joint venture | Contrato, licença de uso, posição contratual | Restrições, consentimentos de terceiros, validade e análise da cessão | Jurídico / pendente | +| Venda/arrendamento de activos, transferência de unidade, sucessão | Activo físico, unidade, contratos e obrigações | Cadeia documental, legitimidade, passivos e autorizações | Jurídico + diligência especializada / pendente | +| Recuperação, expansão, internacionalização | Pedido de capital e finalidade | Informação financeira rotulada, riscos e verificação independente | Revisor + assessor / pendente | +| Imobiliário, propriedades, benfeitorias, investimentos agrícolas | Imóvel/unidade, investimento, direito de uso/ocupação e contrato separados | Área/limites, ocupação, encargos, litígios, finalidade, prazo, autorizações e transmissibilidade | Jurídico fundiário + entidade competente por confirmar / pendente | +| Licenças mineiras/florestais e outras | Licença, titular declarado, entidade emissora e actividade | Fonte competente autorizada, validade, restrições, obrigações e possibilidade de transmissão | Jurídico sectorial + entidade por confirmar / pendente | Termos de uso, base de tratamento, consentimento granular, retenção, conflitos de interesse, prevenção de fraude, denúncias, critérios AML/KYC quando aplicáveis e NDA: todos pendentes de aprovação especializada. Não recolher prova de fundos como obrigação geral nem presumir validade de assinatura electrónica. Política proposta: ausência de evidência bloqueia publicação/acesso; nenhum rótulo indica certificação da Tempo. ## Classificação e ameaças -| Dados | Classificação inicial proposta | Controlos e teste de aceitação | -| --- | --- | --- | -| Teaser aprovado, modalidade, sector, região indicativa, valor rotulado | Público apenas após autorização | Allowlist na API/índice/exportação; teste de reidentificação | -| Triagem, tarefas, estados, origens CRM | Interno | Menor privilégio; auditoria sem conteúdo reservado | -| Empresa, financeiros, contratos, clientes/fornecedores, propostas | Confidencial | Cifragem, escopo por anúncio, grants temporais; teste de acesso cruzado | +| Dados | Classificação inicial proposta | Controlos e teste de aceitação | +| --------------------------------------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------- | +| Teaser aprovado, modalidade, sector, região indicativa, valor rotulado | Público apenas após autorização | Allowlist na API/índice/exportação; teste de reidentificação | +| Triagem, tarefas, estados, origens CRM | Interno | Menor privilégio; auditoria sem conteúdo reservado | +| Empresa, financeiros, contratos, clientes/fornecedores, propostas | Confidencial | Cifragem, escopo por anúncio, grants temporais; teste de acesso cruzado | | Identidade, títulos/licenças integrais, coordenadas exactas, segredos e denúncias | Restrito por defeito até classificação aprovada | Sem índice público; URLs não públicas, revogação/expiração, logs minimizados | Finalidade, base, dono, retenção, eliminação e divulgação precisam de decisão por categoria; nenhum prazo foi inventado. Cifragem existente depende da chave Laravel e não substitui gestão de chaves, mandatos ou política de retenção. -| Ameaça | Controlo planeado | Prova exigida | -| --- | --- | --- | -| Anunciante falso/mandato revogado | Verificação separada de conta, identidade, representação e mandato; revalidação | Publicação negada quando ausente/expirado/revogado | -| IDOR entre negócios ou data rooms | Autorização por utilizador, negócio, documento, versão e grant | Leitura/download/listagem cruzados negados | -| Auto-publicação e corrida de versões | Dupla aprovação, lock transaccional e versão esperada | Auto-aprovação negada; versão obsoleta 409 | -| Reidentificação no mapa/pesquisa | Projecção mínima e política GIS comum | Nenhuma coordenada restrita por filtros, cache ou exportação | -| Malware/ficheiro disfarçado | Quarentena, análise e validação de conteúdo | Nenhum download antes do resultado autorizado; falha do scanner bloqueia | -| Revogação com URL/cache persistente | Revalidar cada acesso, no-store, invalidação e TTL | URL partilhado/expirado não concede acesso | -| Duplicação/replay CRM e webhooks | Chave de idempotência, outbox/inbox, autenticação e reconciliação | Replay não duplica lead; falha externa recuperável | -| Fraude, discriminação ou matching opaco | Sinais explicáveis, fila humana, conflitos e denúncias | Sem contacto/recusa definitiva automática | -| Pipeline contabilizado como receita | Valores por natureza e eventos financeiros aprovados | Comissão/cobrança bloqueadas sem contrato/regra/evento | +| Ameaça | Controlo planeado | Prova exigida | +| --------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Anunciante falso/mandato revogado | Verificação separada de conta, identidade, representação e mandato; revalidação | Publicação negada quando ausente/expirado/revogado | +| IDOR entre negócios ou data rooms | Autorização por utilizador, negócio, documento, versão e grant | Leitura/download/listagem cruzados negados | +| Auto-publicação e corrida de versões | Dupla aprovação, lock transaccional e versão esperada | Auto-aprovação negada; versão obsoleta 409 | +| Reidentificação no mapa/pesquisa | Projecção mínima e política GIS comum | Nenhuma coordenada restrita por filtros, cache ou exportação | +| Malware/ficheiro disfarçado | Quarentena, análise e validação de conteúdo | Nenhum download antes do resultado autorizado; falha do scanner bloqueia | +| Revogação com URL/cache persistente | Revalidar cada acesso, no-store, invalidação e TTL | URL partilhado/expirado não concede acesso | +| Duplicação/replay CRM e webhooks | Chave de idempotência, outbox/inbox, autenticação e reconciliação | Replay não duplica lead; falha externa recuperável | +| Fraude, discriminação ou matching opaco | Sinais explicáveis, fila humana, conflitos e denúncias | Sem contacto/recusa definitiva automática | +| Pipeline contabilizado como receita | Valores por natureza e eventos financeiros aprovados | Comissão/cobrança bloqueadas sem contrato/regra/evento | ## Backlog e escopo piloto proposto diff --git a/docs/investmoz/existing-business/browser-validation.json b/docs/investmoz/existing-business/browser-validation.json index b8814759..100154a5 100644 --- a/docs/investmoz/existing-business/browser-validation.json +++ b/docs/investmoz/existing-business/browser-validation.json @@ -1,27 +1,27 @@ { - "date": "2026-09-29T06:39:38.960Z", - "scope": "Synthetic private create/save and responsive layout in isolated QA; institutional review covered by integration tests", - "observations": [ - { - "scenario": "private_create_and_save", - "passed": true - }, - { - "scenario": "private_submit_with_review_consent", - "passed": true - }, - { - "width": 390, - "breadcrumb": true, - "supportVisible": true, - "overflow": false - }, - { - "width": 1440, - "breadcrumb": true, - "supportVisible": true, - "overflow": false - } - ], - "errors": [] + "date": "2026-09-29T06:39:38.960Z", + "scope": "Synthetic private create/save and responsive layout in isolated QA; institutional review covered by integration tests", + "observations": [ + { + "scenario": "private_create_and_save", + "passed": true + }, + { + "scenario": "private_submit_with_review_consent", + "passed": true + }, + { + "width": 390, + "breadcrumb": true, + "supportVisible": true, + "overflow": false + }, + { + "width": 1440, + "breadcrumb": true, + "supportVisible": true, + "overflow": false + } + ], + "errors": [] } diff --git a/docs/investmoz/existing-business/form-schema.json b/docs/investmoz/existing-business/form-schema.json index 2e4bf46d..b81fe7ca 100644 --- a/docs/investmoz/existing-business/form-schema.json +++ b/docs/investmoz/existing-business/form-schema.json @@ -90,9 +90,7 @@ "type": "text", "max": 500, "when": { - "acting_capacity": [ - "representative" - ] + "acting_capacity": ["representative"] } }, "mandate_document_reference": { @@ -102,9 +100,7 @@ "type": "text", "max": 500, "when": { - "acting_capacity": [ - "representative" - ] + "acting_capacity": ["representative"] } }, "mandate_valid_until": { @@ -114,9 +110,7 @@ "type": "date", "max": 10, "when": { - "acting_capacity": [ - "representative" - ] + "acting_capacity": ["representative"] } }, "disclosure_scope": { @@ -126,9 +120,7 @@ "type": "textarea", "max": 2000, "when": { - "acting_capacity": [ - "representative" - ] + "acting_capacity": ["representative"] } }, "legitimacy_declaration": { @@ -209,9 +201,7 @@ "type": "text", "max": 200, "when": { - "project_stage": [ - "other" - ] + "project_stage": ["other"] } }, "availability": { @@ -233,9 +223,7 @@ "type": "date", "max": 10, "when": { - "availability": [ - "from_date" - ] + "availability": ["from_date"] } }, "valid_until": { @@ -252,10 +240,7 @@ "type": "decimal", "max": 24, "when": { - "value_status": [ - "defined", - "indicative" - ] + "value_status": ["defined", "indicative"] } }, "value_status": { @@ -283,10 +268,7 @@ "financing_sought": "Financiamento procurado" }, "when": { - "value_status": [ - "defined", - "indicative" - ] + "value_status": ["defined", "indicative"] } }, "value_nature": { @@ -301,10 +283,7 @@ "appraised": "Avaliado, sujeito a prova" }, "when": { - "value_status": [ - "defined", - "indicative" - ] + "value_status": ["defined", "indicative"] } }, "currency": { @@ -314,10 +293,7 @@ "type": "text", "max": 3, "when": { - "value_status": [ - "defined", - "indicative" - ] + "value_status": ["defined", "indicative"] } }, "amount_secured": { @@ -327,9 +303,7 @@ "type": "decimal", "max": 24, "when": { - "opportunity_type": [ - "investment_project" - ] + "opportunity_type": ["investment_project"] } }, "investment_total": { @@ -339,9 +313,7 @@ "type": "decimal", "max": 24, "when": { - "opportunity_type": [ - "investment_project" - ] + "opportunity_type": ["investment_project"] } }, "amount_sought": { @@ -351,10 +323,7 @@ "type": "decimal", "max": 24, "when": { - "opportunity_type": [ - "investment_project", - "financing_request" - ] + "opportunity_type": ["investment_project", "financing_request"] } }, "equity_percentage": { @@ -383,10 +352,7 @@ "assets": "Activos" }, "when": { - "opportunity_type": [ - "company_sale", - "asset_sale" - ] + "opportunity_type": ["company_sale", "asset_sale"] } }, "partnership_contribution": { @@ -404,9 +370,7 @@ "other": "Outra" }, "when": { - "opportunity_type": [ - "business_partnership" - ] + "opportunity_type": ["business_partnership"] } }, "asset_modality": { @@ -421,9 +385,7 @@ "other": "Outra" }, "when": { - "opportunity_type": [ - "asset_sale" - ] + "opportunity_type": ["asset_sale"] } }, "concession_entity": { @@ -433,9 +395,7 @@ "type": "text", "max": 300, "when": { - "opportunity_type": [ - "concession_opportunity" - ] + "opportunity_type": ["concession_opportunity"] } }, "concession_reference": { @@ -445,9 +405,7 @@ "type": "textarea", "max": 2000, "when": { - "opportunity_type": [ - "concession_opportunity" - ] + "opportunity_type": ["concession_opportunity"] } }, "supply_description": { @@ -457,9 +415,7 @@ "type": "textarea", "max": 2000, "when": { - "opportunity_type": [ - "supply_opportunity" - ] + "opportunity_type": ["supply_opportunity"] } }, "conditions": { @@ -489,10 +445,7 @@ "type": "territory", "max": 36, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "district_id": { @@ -502,10 +455,7 @@ "type": "territory", "max": 36, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "admin_post_id": { @@ -515,10 +465,7 @@ "type": "territory", "max": 36, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "locality_id": { @@ -528,10 +475,7 @@ "type": "territory", "max": 36, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "location_notes": { @@ -552,10 +496,7 @@ "no": "Não" }, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "disclosure_precision": { @@ -570,10 +511,7 @@ "administrative": "Apenas território administrativo" }, "when": { - "geography_scope": [ - "specific", - "multiple" - ] + "geography_scope": ["specific", "multiple"] } }, "asset_notes": { diff --git a/docs/investmoz/existing-business/implementation-status.md b/docs/investmoz/existing-business/implementation-status.md index 42f59546..9f46636b 100644 --- a/docs/investmoz/existing-business/implementation-status.md +++ b/docs/investmoz/existing-business/implementation-status.md @@ -28,16 +28,16 @@ Não existe publicação neste incremento. A constraint da BD permite apenas `dr ## Matriz das fases e decisões -| Fase | Executado | Lacunas / decisão | -| --- | --- | --- | -| 0 — descoberta | Diagnóstico, ameaças, RACI, matriz jurídica, baseline | Política/base/equipa/piloto não homologados; No-Go institucional mantém-se | -| 1 — modelo | Negócio, objectos tipados separados, mandato pendente, classificação, versões, histórico, rollback | Relações documentais/territoriais detalhadas, titularidade comprovada, revisão de mandatos e retenção homologada pendentes; Go condicionado técnico | -| 2 — submissão/administração | UI e API de rascunho, submissão, consentimento, revisão independente, pedido de informação, retirada | Upload/scanner, verificações documentais, dois níveis de aprovação e publicação ainda não implementados; núcleo privado utilizável, fase parcial | -| 3 — catálogo público | Nenhuma ficha reservada enviada ao catálogo público | Projecção anonimizada, revisão de reidentificação, filtros e mapas MNE pendentes; No-Go para divulgação | -| 4 — CRM/matching | CRM existente preservado; nenhum lead MNE enviado | Deduplicação interessado/negócio/modalidade/período, matching revisto e integrações MNE pendentes | -| 5 — sala segura | Referências declarativas e cifragem privadas | NDA juridicamente aprovado, grants temporais, malware, watermark e diligência não implementados; sem sala segura anunciada | -| 6 — negociação/monetização | Valores reservados rotulados por natureza | Workflow negocial, contrato/regra financeira, facturação e reconciliação pendentes; nenhuma cobrança | -| 7 — piloto/operação | Backup/restauro, migrations locais, reconciliação, testes e build | Piloto real, UAT institucional, carga, recuperação MNE completa, PT/EN completo e decisão formal de produção pendentes | +| Fase | Executado | Lacunas / decisão | +| --------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 — descoberta | Diagnóstico, ameaças, RACI, matriz jurídica, baseline | Política/base/equipa/piloto não homologados; No-Go institucional mantém-se | +| 1 — modelo | Negócio, objectos tipados separados, mandato pendente, classificação, versões, histórico, rollback | Relações documentais/territoriais detalhadas, titularidade comprovada, revisão de mandatos e retenção homologada pendentes; Go condicionado técnico | +| 2 — submissão/administração | UI e API de rascunho, submissão, consentimento, revisão independente, pedido de informação, retirada | Upload/scanner, verificações documentais, dois níveis de aprovação e publicação ainda não implementados; núcleo privado utilizável, fase parcial | +| 3 — catálogo público | Nenhuma ficha reservada enviada ao catálogo público | Projecção anonimizada, revisão de reidentificação, filtros e mapas MNE pendentes; No-Go para divulgação | +| 4 — CRM/matching | CRM existente preservado; nenhum lead MNE enviado | Deduplicação interessado/negócio/modalidade/período, matching revisto e integrações MNE pendentes | +| 5 — sala segura | Referências declarativas e cifragem privadas | NDA juridicamente aprovado, grants temporais, malware, watermark e diligência não implementados; sem sala segura anunciada | +| 6 — negociação/monetização | Valores reservados rotulados por natureza | Workflow negocial, contrato/regra financeira, facturação e reconciliação pendentes; nenhuma cobrança | +| 7 — piloto/operação | Backup/restauro, migrations locais, reconciliação, testes e build | Piloto real, UAT institucional, carga, recuperação MNE completa, PT/EN completo e decisão formal de produção pendentes | Não se declara conclusão integral do prompt. Aprovação para desenvolver não torna disponíveis os documentos, mandatos ou regras que faltam. O próximo trabalho técnico é a evidência documental e a governação da validação, antes da publicação; não é necessário repetir autorização de desenvolvimento. diff --git a/docs/investmoz/existing-business/openapi.json b/docs/investmoz/existing-business/openapi.json index 55710f41..7a860965 100644 --- a/docs/investmoz/existing-business/openapi.json +++ b/docs/investmoz/existing-business/openapi.json @@ -1,3226 +1,2420 @@ { - "openapi": "3.1.0", - "info": { - "title": "InvestMoz — private existing-business intake", - "version": "1.2.0", - "description": "Internal preparation and review only. No publication, certification, legal transfer, NDA, payment or external CRM operation." - }, - "servers": [ - { - "url": "/api/v1/existing-business" - } - ], - "paths": { - "/listings": { - "get": { - "summary": "List own active records, 20 per page", - "parameters": [ - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "records", - "management", - "modalities" - ], - "properties": { - "records": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Business" - } - }, - "total": { - "type": "integer" - } - } - }, - "management": { - "type": "boolean" - }, - "modalities": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - } - }, - "post": { - "summary": "Create private draft", - "parameters": [], - "security": [ - { - "session": [] - } - ], - "responses": { - "201": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Business" - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "modality", - "classification", - "reason", - "payload" - ], - "properties": { - "modality": { - "type": "string" - }, - "classification": { - "enum": [ - "internal", - "confidential", - "restricted" - ] - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - }, - "payload": { - "type": "object", - "additionalProperties": false, - "required": [ - "title" - ], - "properties": { - "title": { - "type": "string", - "maxLength": 200, - "description": "Proposed title" - }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 10000, - "description": "Summary: what is available, sought and key conditions" - }, - "declared_reason": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Reason and objective" - }, - "source_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Source reference" - }, - "valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Interest deadline (optional)" - }, - "proponent_type": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "organization", - "sole_trader", - "person", - "public_entity", - null - ], - "description": "Who is presenting the opportunity?" - }, - "proponent_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Name" - }, - "country": { - "type": [ - "string", - "null" - ], - "maxLength": 2, - "description": "Country of origin or incorporation (two-letter ISO code)" - }, - "tax_identifier": { - "type": [ - "string", - "null" - ], - "maxLength": 50, - "description": "NUIT or identifier (internal, when applicable)" - }, - "contact_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Contact person name" - }, - "contact_email": { - "type": [ - "string", - "null" - ], - "maxLength": 254, - "description": "Email (provide email or phone)" - }, - "contact_phone": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "description": "Phone (provide phone or email)" - }, - "contact_preference": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "through_portal", - "approved_business_contact", - null - ], - "description": "Contact preference" - }, - "acting_capacity": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "holder", - "representative", - null - ], - "description": "Acting capacity" - }, - "declared_holder": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Entity or person represented" - }, - "mandate_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Authorization document reference" - }, - "mandate_document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Internal authorization document reference (do not upload here)" - }, - "mandate_valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Mandate expiry" - }, - "disclosure_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Limits of disclosure authorization" - }, - "legitimacy_declaration": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I declare authority to submit" - }, - "opportunity_type": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "investment_project", - "company_sale", - "asset_sale", - "business_partnership", - "financing_request", - "concession_opportunity", - "supply_opportunity", - null - ], - "description": "Opportunity type" - }, - "subsector_activity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Subsector and activity (by name)" - }, - "project_stage": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "idea", - "study", - "preparation", - "implementation", - "operating", - "suspended", - "other", - null - ], - "description": "Project or business status" - }, - "project_stage_other": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Specify the status" - }, - "availability": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "available", - "from_date", - "negotiating", - null - ], - "description": "Availability" - }, - "available_from": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Available from" - }, - "asking_value": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Value" - }, - "value_status": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "defined", - "indicative", - "consultation", - "unknown", - null - ], - "description": "Is the value defined?" - }, - "value_meaning": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking_price", - "investment_estimate", - "financing_sought", - null - ], - "description": "Meaning of the value" - }, - "value_nature": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking", - "indicative", - "appraised", - null - ], - "description": "Value basis" - }, - "currency": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "description": "Currency (three-letter code)" - }, - "amount_secured": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Secured amount (projects)" - }, - "investment_total": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Estimated total investment (projects)" - }, - "amount_sought": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Financing sought" - }, - "equity_percentage": { - "type": [ - "string", - "null" - ], - "maxLength": 8, - "description": "Available equity (%)" - }, - "funds_use": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Use of funds" - }, - "asset_deal_kind": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "whole_business", - "participation", - "assets", - null - ], - "description": "Type of sale" - }, - "partnership_contribution": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "capital", - "technology", - "management", - "equipment", - "distribution", - "other", - null - ], - "description": "Contribution sought" - }, - "asset_modality": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "sale", - "lease", - "other", - null - ], - "description": "Asset arrangement" - }, - "concession_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 300, - "description": "Concession authority" - }, - "concession_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official reference, procedure and deadline" - }, - "supply_description": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Good or service, quantity/capacity, destination and timing" - }, - "conditions": { - "type": [ - "string", - "null" - ], - "maxLength": 4000, - "description": "Additional conditions" - }, - "geography_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "specific", - "multiple", - "national", - "digital", - null - ], - "description": "Geographic scope" - }, - "province_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Province or Maputo City" - }, - "district_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "District" - }, - "admin_post_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Administrative post" - }, - "locality_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Locality" - }, - "location_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Address or reference (internal)" - }, - "location_unknown": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I do not know the exact location" - }, - "disclosure_precision": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "exact", - "approximate", - "administrative", - null - ], - "description": "Disclosure precision" - }, - "asset_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Declared assets and rights summary" - }, - "source_provider": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Who supplied the information?" - }, - "source_date": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Source/information date" - }, - "official_source_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Official source authority (if any)" - }, - "official_source_document": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Related official document" - }, - "official_source_url": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official source link" - }, - "official_source_period": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Official source period" - }, - "document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Document references (not validated files)" - }, - "confirm_information": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm the information reflects what I know and identify estimates or pending items." - }, - "confirm_disclosure_authority": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm I am authorized to submit the opportunity and disclose selected content." - }, - "confirm_no_publication_guarantee": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I understand submission is not publication, official approval or an investment guarantee." - }, - "wizard_version": { - "type": [ - "string", - "null" - ], - "enum": [ - "1", - null - ] - }, - "wizard_step": { - "type": [ - "string", - "null" - ], - "pattern": "^[1-8]$" - } - } - }, - "sector_id": { - "type": [ - "integer", - "null" - ], - "description": "Existing active public.sectors identifier; required on wizard submission." - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } + "openapi": "3.1.0", + "info": { + "title": "InvestMoz — private existing-business intake", + "version": "1.2.0", + "description": "Internal preparation and review only. No publication, certification, legal transfer, NDA, payment or external CRM operation." }, - "/reviews": { - "get": { - "summary": "List submitted records for designated reviewers with MFA; excludes own records", - "parameters": [], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "records": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Business" - } - } - } - }, - "management": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } + "servers": [ + { + "url": "/api/v1/existing-business" } - } - }, - "/listings/{id}": { - "get": { - "summary": "Read own record or currently reviewable submission", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "record": { - "$ref": "#/components/schemas/Business" - }, - "canEdit": { - "type": "boolean" - }, - "owner": { - "type": "boolean" - }, - "canReview": { - "type": "boolean" - }, - "history": { - "type": "array", - "items": { - "type": "object", - "properties": { - "version_no": { - "type": "integer" - }, - "action": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "note": { - "type": "string" - } - } - } - }, - "verifications": { - "type": "array", - "items": { - "type": "object", - "required": [ - "version_no", - "check", - "outcome", - "evidence_reference", - "note" - ], - "properties": { - "version_no": { + ], + "paths": { + "/listings": { + "get": { + "summary": "List own active records, 20 per page", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { "type": "integer", "minimum": 1 - }, - "object_id": { - "type": [ - "string", - "null" - ], - "format": "uuid" - }, - "check": { - "enum": [ - "identity", - "representation", - "declared_ownership", - "documentary_ownership", - "document_received", - "document_validated", - "independent_valuation", - "land_right_documented", - "license_confirmed", - "validity", - "legal_transfer", - "prior_authorization" - ] - }, - "outcome": { - "enum": [ - "confirmed", - "rejected", - "inconclusive", - "revoked" - ] - }, - "evidence_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "note": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "valid_until": { - "type": [ - "string", - "null" - ], - "format": "date", - "description": "Required for confirmed findings; must be today or later." - }, - "effective_outcome": { - "enum": [ - "confirmed", - "rejected", - "inconclusive", - "revoked", - "stale", - "expired" - ] - }, - "content_version": { - "type": "integer" - }, - "created_at": { - "type": "string" - } } - } } - } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "records", + "management", + "modalities" + ], + "properties": { + "records": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Business" + } + }, + "total": { + "type": "integer" + } + } + }, + "management": { + "type": "boolean" + }, + "modalities": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } } - } + }, + "post": { + "summary": "Create private draft", + "parameters": [], + "security": [ + { + "session": [] + } + ], + "responses": { + "201": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Business" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "modality", + "classification", + "reason", + "payload" + ], + "properties": { + "modality": { + "type": "string" + }, + "classification": { + "enum": [ + "internal", + "confidential", + "restricted" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "maxLength": 200, + "description": "Proposed title" + }, + "description": { + "type": ["string", "null"], + "maxLength": 10000, + "description": "Summary: what is available, sought and key conditions" + }, + "declared_reason": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Reason and objective" + }, + "source_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Source reference" + }, + "valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Interest deadline (optional)" + }, + "proponent_type": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "organization", + "sole_trader", + "person", + "public_entity", + null + ], + "description": "Who is presenting the opportunity?" + }, + "proponent_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Name" + }, + "country": { + "type": ["string", "null"], + "maxLength": 2, + "description": "Country of origin or incorporation (two-letter ISO code)" + }, + "tax_identifier": { + "type": ["string", "null"], + "maxLength": 50, + "description": "NUIT or identifier (internal, when applicable)" + }, + "contact_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Contact person name" + }, + "contact_email": { + "type": ["string", "null"], + "maxLength": 254, + "description": "Email (provide email or phone)" + }, + "contact_phone": { + "type": ["string", "null"], + "maxLength": 40, + "description": "Phone (provide phone or email)" + }, + "contact_preference": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "through_portal", + "approved_business_contact", + null + ], + "description": "Contact preference" + }, + "acting_capacity": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "holder", + "representative", + null + ], + "description": "Acting capacity" + }, + "declared_holder": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Entity or person represented" + }, + "mandate_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Authorization document reference" + }, + "mandate_document_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Internal authorization document reference (do not upload here)" + }, + "mandate_valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Mandate expiry" + }, + "disclosure_scope": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Limits of disclosure authorization" + }, + "legitimacy_declaration": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I declare authority to submit" + }, + "opportunity_type": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "investment_project", + "company_sale", + "asset_sale", + "business_partnership", + "financing_request", + "concession_opportunity", + "supply_opportunity", + null + ], + "description": "Opportunity type" + }, + "subsector_activity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Subsector and activity (by name)" + }, + "project_stage": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "idea", + "study", + "preparation", + "implementation", + "operating", + "suspended", + "other", + null + ], + "description": "Project or business status" + }, + "project_stage_other": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Specify the status" + }, + "availability": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "available", + "from_date", + "negotiating", + null + ], + "description": "Availability" + }, + "available_from": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Available from" + }, + "asking_value": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Value" + }, + "value_status": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "defined", + "indicative", + "consultation", + "unknown", + null + ], + "description": "Is the value defined?" + }, + "value_meaning": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking_price", + "investment_estimate", + "financing_sought", + null + ], + "description": "Meaning of the value" + }, + "value_nature": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking", + "indicative", + "appraised", + null + ], + "description": "Value basis" + }, + "currency": { + "type": ["string", "null"], + "maxLength": 3, + "description": "Currency (three-letter code)" + }, + "amount_secured": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Secured amount (projects)" + }, + "investment_total": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Estimated total investment (projects)" + }, + "amount_sought": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Financing sought" + }, + "equity_percentage": { + "type": ["string", "null"], + "maxLength": 8, + "description": "Available equity (%)" + }, + "funds_use": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Use of funds" + }, + "asset_deal_kind": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "whole_business", + "participation", + "assets", + null + ], + "description": "Type of sale" + }, + "partnership_contribution": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "capital", + "technology", + "management", + "equipment", + "distribution", + "other", + null + ], + "description": "Contribution sought" + }, + "asset_modality": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "sale", + "lease", + "other", + null + ], + "description": "Asset arrangement" + }, + "concession_entity": { + "type": ["string", "null"], + "maxLength": 300, + "description": "Concession authority" + }, + "concession_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official reference, procedure and deadline" + }, + "supply_description": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Good or service, quantity/capacity, destination and timing" + }, + "conditions": { + "type": ["string", "null"], + "maxLength": 4000, + "description": "Additional conditions" + }, + "geography_scope": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "specific", + "multiple", + "national", + "digital", + null + ], + "description": "Geographic scope" + }, + "province_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Province or Maputo City" + }, + "district_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "District" + }, + "admin_post_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Administrative post" + }, + "locality_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Locality" + }, + "location_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Address or reference (internal)" + }, + "location_unknown": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I do not know the exact location" + }, + "disclosure_precision": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "exact", + "approximate", + "administrative", + null + ], + "description": "Disclosure precision" + }, + "asset_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Declared assets and rights summary" + }, + "source_provider": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Who supplied the information?" + }, + "source_date": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Source/information date" + }, + "official_source_entity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Official source authority (if any)" + }, + "official_source_document": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Related official document" + }, + "official_source_url": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official source link" + }, + "official_source_period": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Official source period" + }, + "document_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Document references (not validated files)" + }, + "confirm_information": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm the information reflects what I know and identify estimates or pending items." + }, + "confirm_disclosure_authority": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm I am authorized to submit the opportunity and disclose selected content." + }, + "confirm_no_publication_guarantee": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I understand submission is not publication, official approval or an investment guarantee." + }, + "wizard_version": { + "type": ["string", "null"], + "enum": ["1", null] + }, + "wizard_step": { + "type": ["string", "null"], + "pattern": "^[1-8]$" + } + } + }, + "sector_id": { + "type": ["integer", "null"], + "description": "Existing active public.sectors identifier; required on wizard submission." + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - } - }, - "put": { - "summary": "Update owned draft or request for information; optimistic version required", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Business" - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { + "/reviews": { + "get": { + "summary": "List submitted records for designated reviewers with MFA; excludes own records", + "parameters": [], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "records": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Business" + } + } + } + }, + "management": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + } + } + }, + "/listings/{id}": { + "get": { + "summary": "Read own record or currently reviewable submission", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "record": { + "$ref": "#/components/schemas/Business" + }, + "canEdit": { + "type": "boolean" + }, + "owner": { + "type": "boolean" + }, + "canReview": { + "type": "boolean" + }, + "history": { + "type": "array", + "items": { + "type": "object", + "properties": { + "version_no": { + "type": "integer" + }, + "action": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "note": { + "type": "string" + } + } + } + }, + "verifications": { + "type": "array", + "items": { + "type": "object", + "required": [ + "version_no", + "check", + "outcome", + "evidence_reference", + "note" + ], + "properties": { + "version_no": { + "type": "integer", + "minimum": 1 + }, + "object_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "check": { + "enum": [ + "identity", + "representation", + "declared_ownership", + "documentary_ownership", + "document_received", + "document_validated", + "independent_valuation", + "land_right_documented", + "license_confirmed", + "validity", + "legal_transfer", + "prior_authorization" + ] + }, + "outcome": { + "enum": [ + "confirmed", + "rejected", + "inconclusive", + "revoked" + ] + }, + "evidence_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "note": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "valid_until": { + "type": [ + "string", + "null" + ], + "format": "date", + "description": "Required for confirmed findings; must be today or later." + }, + "effective_outcome": { + "enum": [ + "confirmed", + "rejected", + "inconclusive", + "revoked", + "stale", + "expired" + ] + }, + "content_version": { + "type": "integer" + }, + "created_at": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + } + }, + "put": { + "summary": "Update owned draft or request for information; optimistic version required", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Business" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "modality", + "classification", + "reason", + "payload", + "version_no" + ], + "properties": { + "modality": { + "type": "string" + }, + "classification": { + "enum": [ + "internal", + "confidential", + "restricted" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "maxLength": 200, + "description": "Proposed title" + }, + "description": { + "type": ["string", "null"], + "maxLength": 10000, + "description": "Summary: what is available, sought and key conditions" + }, + "declared_reason": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Reason and objective" + }, + "source_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Source reference" + }, + "valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Interest deadline (optional)" + }, + "proponent_type": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "organization", + "sole_trader", + "person", + "public_entity", + null + ], + "description": "Who is presenting the opportunity?" + }, + "proponent_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Name" + }, + "country": { + "type": ["string", "null"], + "maxLength": 2, + "description": "Country of origin or incorporation (two-letter ISO code)" + }, + "tax_identifier": { + "type": ["string", "null"], + "maxLength": 50, + "description": "NUIT or identifier (internal, when applicable)" + }, + "contact_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Contact person name" + }, + "contact_email": { + "type": ["string", "null"], + "maxLength": 254, + "description": "Email (provide email or phone)" + }, + "contact_phone": { + "type": ["string", "null"], + "maxLength": 40, + "description": "Phone (provide phone or email)" + }, + "contact_preference": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "through_portal", + "approved_business_contact", + null + ], + "description": "Contact preference" + }, + "acting_capacity": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "holder", + "representative", + null + ], + "description": "Acting capacity" + }, + "declared_holder": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Entity or person represented" + }, + "mandate_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Authorization document reference" + }, + "mandate_document_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Internal authorization document reference (do not upload here)" + }, + "mandate_valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Mandate expiry" + }, + "disclosure_scope": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Limits of disclosure authorization" + }, + "legitimacy_declaration": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I declare authority to submit" + }, + "opportunity_type": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "investment_project", + "company_sale", + "asset_sale", + "business_partnership", + "financing_request", + "concession_opportunity", + "supply_opportunity", + null + ], + "description": "Opportunity type" + }, + "subsector_activity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Subsector and activity (by name)" + }, + "project_stage": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "idea", + "study", + "preparation", + "implementation", + "operating", + "suspended", + "other", + null + ], + "description": "Project or business status" + }, + "project_stage_other": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Specify the status" + }, + "availability": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "available", + "from_date", + "negotiating", + null + ], + "description": "Availability" + }, + "available_from": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Available from" + }, + "asking_value": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Value" + }, + "value_status": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "defined", + "indicative", + "consultation", + "unknown", + null + ], + "description": "Is the value defined?" + }, + "value_meaning": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking_price", + "investment_estimate", + "financing_sought", + null + ], + "description": "Meaning of the value" + }, + "value_nature": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking", + "indicative", + "appraised", + null + ], + "description": "Value basis" + }, + "currency": { + "type": ["string", "null"], + "maxLength": 3, + "description": "Currency (three-letter code)" + }, + "amount_secured": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Secured amount (projects)" + }, + "investment_total": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Estimated total investment (projects)" + }, + "amount_sought": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Financing sought" + }, + "equity_percentage": { + "type": ["string", "null"], + "maxLength": 8, + "description": "Available equity (%)" + }, + "funds_use": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Use of funds" + }, + "asset_deal_kind": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "whole_business", + "participation", + "assets", + null + ], + "description": "Type of sale" + }, + "partnership_contribution": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "capital", + "technology", + "management", + "equipment", + "distribution", + "other", + null + ], + "description": "Contribution sought" + }, + "asset_modality": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "sale", + "lease", + "other", + null + ], + "description": "Asset arrangement" + }, + "concession_entity": { + "type": ["string", "null"], + "maxLength": 300, + "description": "Concession authority" + }, + "concession_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official reference, procedure and deadline" + }, + "supply_description": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Good or service, quantity/capacity, destination and timing" + }, + "conditions": { + "type": ["string", "null"], + "maxLength": 4000, + "description": "Additional conditions" + }, + "geography_scope": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "specific", + "multiple", + "national", + "digital", + null + ], + "description": "Geographic scope" + }, + "province_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Province or Maputo City" + }, + "district_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "District" + }, + "admin_post_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Administrative post" + }, + "locality_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Locality" + }, + "location_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Address or reference (internal)" + }, + "location_unknown": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I do not know the exact location" + }, + "disclosure_precision": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "exact", + "approximate", + "administrative", + null + ], + "description": "Disclosure precision" + }, + "asset_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Declared assets and rights summary" + }, + "source_provider": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Who supplied the information?" + }, + "source_date": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Source/information date" + }, + "official_source_entity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Official source authority (if any)" + }, + "official_source_document": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Related official document" + }, + "official_source_url": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official source link" + }, + "official_source_period": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Official source period" + }, + "document_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Document references (not validated files)" + }, + "confirm_information": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm the information reflects what I know and identify estimates or pending items." + }, + "confirm_disclosure_authority": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm I am authorized to submit the opportunity and disclose selected content." + }, + "confirm_no_publication_guarantee": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I understand submission is not publication, official approval or an investment guarantee." + }, + "wizard_version": { + "type": ["string", "null"], + "enum": ["1", null] + }, + "wizard_step": { + "type": ["string", "null"], + "pattern": "^[1-8]$" + } + } + }, + "version_no": { + "type": "integer", + "minimum": 1 + }, + "sector_id": { + "type": ["integer", "null"], + "description": "Existing active public.sectors identifier; required on wizard submission." + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." + } + }, + "/listings/{id}/transitions": { + "post": { + "summary": "Submit, begin review, request information or withdraw", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Business" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["version_no", "action", "note"], + "properties": { + "version_no": { + "type": "integer", + "minimum": 1 + }, + "action": { + "enum": [ + "submitted", + "in_review", + "needs_information", + "withdrawn" + ] + }, + "note": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "review_consent": { + "type": "boolean", + "description": "Must be true for submission. Stored in encrypted review evidence. Does not authorize publication." + }, + "confirmation_code": { + "type": ["string", "null"], + "description": "Exact business code required for wizard_version=1 submission, in addition to internal review consent." + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." + } + }, + "/listings/{id}/objects": { + "post": { + "summary": "Declare a separate restricted object; increments aggregate version without granting verification", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "201": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version_no": { + "type": "integer" + }, + "verification_status": { + "const": "pending" + } + } + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "version_no", + "reason", + "payload" + ], + "properties": { + "kind": { + "enum": [ + "company", + "participation", + "asset", + "property", + "unit", + "improvement", + "agricultural_investment", + "land_right", + "contract", + "license", + "authorization", + "encumbrance", + "valuation", + "capital_request" + ] + }, + "version_no": { + "type": "integer", + "minimum": 1 + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": [ + "label", + "source_reference" + ], + "properties": { + "label": { + "type": ["string", "null"] + }, + "declared_holder": { + "type": ["string", "null"] + }, + "document_reference": { + "type": ["string", "null"] + }, + "source_reference": { + "type": ["string", "null"] + }, + "competent_entity": { + "type": ["string", "null"] + }, + "purpose": { + "type": ["string", "null"] + }, + "location_description": { + "type": ["string", "null"] + }, + "boundaries_description": { + "type": ["string", "null"] + }, + "occupancy": { + "type": ["string", "null"] + }, + "condition": { + "type": ["string", "null"] + }, + "encumbrances": { + "type": ["string", "null"] + }, + "restrictions": { + "type": ["string", "null"] + }, + "required_authorizations": { + "type": ["string", "null"] + }, + "transfer_condition": { + "type": ["string", "null"] + }, + "contract_type": { + "type": ["string", "null"] + }, + "valid_from": { + "type": ["string", "null"] + }, + "valid_until": { + "type": ["string", "null"] + }, + "area": { + "type": ["string", "null"] + }, + "area_unit": { + "type": ["string", "null"] + }, + "amount": { + "type": ["string", "null"] + }, + "currency": { + "type": ["string", "null"] + }, + "value_kind": { + "type": ["string", "null"] + }, + "participation_percent": { + "type": ["string", "null"] + } + } + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." + } + }, + "/listings/{id}/objects/{object}": { + "put": { + "summary": "Update scoped object with immutable kind; amount requires currency and value kind; area requires unit", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "object", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version_no": { + "type": "integer" + }, + "verification_status": { + "const": "pending" + } + } + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "version_no", + "reason", + "payload" + ], + "properties": { + "kind": { + "enum": [ + "company", + "participation", + "asset", + "property", + "unit", + "improvement", + "agricultural_investment", + "land_right", + "contract", + "license", + "authorization", + "encumbrance", + "valuation", + "capital_request" + ] + }, + "version_no": { + "type": "integer", + "minimum": 1 + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": [ + "label", + "source_reference" + ], + "properties": { + "label": { + "type": ["string", "null"] + }, + "declared_holder": { + "type": ["string", "null"] + }, + "document_reference": { + "type": ["string", "null"] + }, + "source_reference": { + "type": ["string", "null"] + }, + "competent_entity": { + "type": ["string", "null"] + }, + "purpose": { + "type": ["string", "null"] + }, + "location_description": { + "type": ["string", "null"] + }, + "boundaries_description": { + "type": ["string", "null"] + }, + "occupancy": { + "type": ["string", "null"] + }, + "condition": { + "type": ["string", "null"] + }, + "encumbrances": { + "type": ["string", "null"] + }, + "restrictions": { + "type": ["string", "null"] + }, + "required_authorizations": { + "type": ["string", "null"] + }, + "transfer_condition": { + "type": ["string", "null"] + }, + "contract_type": { + "type": ["string", "null"] + }, + "valid_from": { + "type": ["string", "null"] + }, + "valid_until": { + "type": ["string", "null"] + }, + "area": { + "type": ["string", "null"] + }, + "area_unit": { + "type": ["string", "null"] + }, + "amount": { + "type": ["string", "null"] + }, + "currency": { + "type": ["string", "null"] + }, + "value_kind": { + "type": ["string", "null"] + }, + "participation_percent": { + "type": ["string", "null"] + } + } + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." + } + }, + "/listings/{id}/verifications": { + "post": { + "summary": "Record independent finding while in_review; immutable encrypted evidence, optimistic version and scoped object. Does not publish or certify the business.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Private response; Cache-Control private, no-store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Business" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Account, ownership or current reviewer/MFA policy rejected" + }, + "404": { + "description": "Module disabled or resource unavailable" + }, + "409": { + "description": "Stale version or invalid transition" + }, + "422": { + "description": "Validation error" + }, + "429": { + "description": "Rate limited" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "version_no", + "check", + "outcome", + "evidence_reference", + "note" + ], + "properties": { + "version_no": { + "type": "integer", + "minimum": 1 + }, + "object_id": { + "type": ["string", "null"], + "format": "uuid" + }, + "check": { + "enum": [ + "identity", + "representation", + "declared_ownership", + "documentary_ownership", + "document_received", + "document_validated", + "independent_valuation", + "land_right_documented", + "license_confirmed", + "validity", + "legal_transfer", + "prior_authorization" + ] + }, + "outcome": { + "enum": [ + "confirmed", + "rejected", + "inconclusive", + "revoked" + ] + }, + "evidence_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "note": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "valid_until": { + "type": ["string", "null"], + "format": "date", + "description": "Required for confirmed findings; must be today or later." + } + } + } + } + } + }, + "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." + } + } + }, + "components": { + "securitySchemes": { + "session": { + "type": "apiKey", + "in": "cookie", + "name": "session_cookie", + "description": "Actual Laravel cookie name comes from deployment configuration; no bearer token support." + } + }, + "schemas": { + "Business": { "type": "object", "required": [ - "modality", - "classification", - "reason", - "payload", - "version_no" + "id", + "code", + "modality", + "status", + "classification", + "version_no", + "payload" ], "properties": { - "modality": { - "type": "string" - }, - "classification": { - "enum": [ - "internal", - "confidential", - "restricted" - ] - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - }, - "payload": { - "type": "object", - "additionalProperties": false, - "required": [ - "title" - ], - "properties": { - "title": { + "id": { "type": "string", - "maxLength": 200, - "description": "Proposed title" - }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 10000, - "description": "Summary: what is available, sought and key conditions" - }, - "declared_reason": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Reason and objective" - }, - "source_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Source reference" - }, - "valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Interest deadline (optional)" - }, - "proponent_type": { - "type": [ - "string", - "null" - ], - "maxLength": 30, + "format": "uuid" + }, + "code": { + "type": "string" + }, + "modality": { + "type": "string" + }, + "status": { "enum": [ - "organization", - "sole_trader", - "person", - "public_entity", - null - ], - "description": "Who is presenting the opportunity?" - }, - "proponent_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Name" - }, - "country": { - "type": [ - "string", - "null" - ], - "maxLength": 2, - "description": "Country of origin or incorporation (two-letter ISO code)" - }, - "tax_identifier": { - "type": [ - "string", - "null" - ], - "maxLength": 50, - "description": "NUIT or identifier (internal, when applicable)" - }, - "contact_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Contact person name" - }, - "contact_email": { - "type": [ - "string", - "null" - ], - "maxLength": 254, - "description": "Email (provide email or phone)" - }, - "contact_phone": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "description": "Phone (provide phone or email)" - }, - "contact_preference": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "through_portal", - "approved_business_contact", - null - ], - "description": "Contact preference" - }, - "acting_capacity": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "holder", - "representative", - null - ], - "description": "Acting capacity" - }, - "declared_holder": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Entity or person represented" - }, - "mandate_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Authorization document reference" - }, - "mandate_document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Internal authorization document reference (do not upload here)" - }, - "mandate_valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Mandate expiry" - }, - "disclosure_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Limits of disclosure authorization" - }, - "legitimacy_declaration": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I declare authority to submit" - }, - "opportunity_type": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "investment_project", - "company_sale", - "asset_sale", - "business_partnership", - "financing_request", - "concession_opportunity", - "supply_opportunity", - null - ], - "description": "Opportunity type" - }, - "subsector_activity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Subsector and activity (by name)" - }, - "project_stage": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "idea", - "study", - "preparation", - "implementation", - "operating", - "suspended", - "other", - null - ], - "description": "Project or business status" - }, - "project_stage_other": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Specify the status" - }, - "availability": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "available", - "from_date", - "negotiating", - null - ], - "description": "Availability" - }, - "available_from": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Available from" - }, - "asking_value": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Value" - }, - "value_status": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "defined", - "indicative", - "consultation", - "unknown", - null - ], - "description": "Is the value defined?" - }, - "value_meaning": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking_price", - "investment_estimate", - "financing_sought", - null - ], - "description": "Meaning of the value" - }, - "value_nature": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking", - "indicative", - "appraised", - null - ], - "description": "Value basis" - }, - "currency": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "description": "Currency (three-letter code)" - }, - "amount_secured": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Secured amount (projects)" - }, - "investment_total": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Estimated total investment (projects)" - }, - "amount_sought": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Financing sought" - }, - "equity_percentage": { - "type": [ - "string", - "null" - ], - "maxLength": 8, - "description": "Available equity (%)" - }, - "funds_use": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Use of funds" - }, - "asset_deal_kind": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "whole_business", - "participation", - "assets", - null - ], - "description": "Type of sale" - }, - "partnership_contribution": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "capital", - "technology", - "management", - "equipment", - "distribution", - "other", - null - ], - "description": "Contribution sought" - }, - "asset_modality": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "sale", - "lease", - "other", - null - ], - "description": "Asset arrangement" - }, - "concession_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 300, - "description": "Concession authority" - }, - "concession_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official reference, procedure and deadline" - }, - "supply_description": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Good or service, quantity/capacity, destination and timing" - }, - "conditions": { - "type": [ - "string", - "null" - ], - "maxLength": 4000, - "description": "Additional conditions" - }, - "geography_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "specific", - "multiple", - "national", - "digital", - null - ], - "description": "Geographic scope" - }, - "province_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Province or Maputo City" - }, - "district_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "District" - }, - "admin_post_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Administrative post" - }, - "locality_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Locality" - }, - "location_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Address or reference (internal)" - }, - "location_unknown": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I do not know the exact location" - }, - "disclosure_precision": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "exact", - "approximate", - "administrative", - null - ], - "description": "Disclosure precision" - }, - "asset_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Declared assets and rights summary" - }, - "source_provider": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Who supplied the information?" - }, - "source_date": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Source/information date" - }, - "official_source_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Official source authority (if any)" - }, - "official_source_document": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Related official document" - }, - "official_source_url": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official source link" - }, - "official_source_period": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Official source period" - }, - "document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Document references (not validated files)" - }, - "confirm_information": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm the information reflects what I know and identify estimates or pending items." - }, - "confirm_disclosure_authority": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm I am authorized to submit the opportunity and disclose selected content." - }, - "confirm_no_publication_guarantee": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I understand submission is not publication, official approval or an investment guarantee." - }, - "wizard_version": { - "type": [ - "string", - "null" - ], - "enum": [ - "1", - null + "draft", + "submitted", + "in_review", + "needs_information", + "withdrawn" ] - }, - "wizard_step": { - "type": [ - "string", - "null" - ], - "pattern": "^[1-8]$" - } - } - }, - "version_no": { - "type": "integer", - "minimum": 1 - }, - "sector_id": { - "type": [ - "integer", - "null" - ], - "description": "Existing active public.sectors identifier; required on wizard submission." - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } - }, - "/listings/{id}/transitions": { - "post": { - "summary": "Submit, begin review, request information or withdraw", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Business" - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "version_no", - "action", - "note" - ], - "properties": { - "version_no": { - "type": "integer", - "minimum": 1 - }, - "action": { - "enum": [ - "submitted", - "in_review", - "needs_information", - "withdrawn" - ] - }, - "note": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "review_consent": { - "type": "boolean", - "description": "Must be true for submission. Stored in encrypted review evidence. Does not authorize publication." - }, - "confirmation_code": { - "type": [ - "string", - "null" - ], - "description": "Exact business code required for wizard_version=1 submission, in addition to internal review consent." - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } - }, - "/listings/{id}/objects": { - "post": { - "summary": "Declare a separate restricted object; increments aggregate version without granting verification", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "201": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" + }, + "classification": { + "enum": ["internal", "confidential", "restricted"] }, "version_no": { - "type": "integer" + "type": "integer", + "minimum": 1 }, - "verification_status": { - "const": "pending" - } - } - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "version_no", - "reason", - "payload" - ], - "properties": { - "kind": { - "enum": [ - "company", - "participation", - "asset", - "property", - "unit", - "improvement", - "agricultural_investment", - "land_right", - "contract", - "license", - "authorization", - "encumbrance", - "valuation", - "capital_request" - ] - }, - "version_no": { - "type": "integer", - "minimum": 1 - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - }, - "payload": { - "type": "object", - "additionalProperties": false, - "required": [ - "label", - "source_reference" - ], - "properties": { - "label": { - "type": [ - "string", - "null" - ] - }, - "declared_holder": { - "type": [ - "string", - "null" - ] - }, - "document_reference": { - "type": [ - "string", - "null" - ] - }, - "source_reference": { - "type": [ - "string", - "null" - ] - }, - "competent_entity": { - "type": [ - "string", - "null" - ] - }, - "purpose": { - "type": [ - "string", - "null" - ] - }, - "location_description": { - "type": [ - "string", - "null" - ] - }, - "boundaries_description": { - "type": [ - "string", - "null" - ] - }, - "occupancy": { - "type": [ - "string", - "null" - ] - }, - "condition": { - "type": [ - "string", - "null" - ] - }, - "encumbrances": { - "type": [ - "string", - "null" - ] - }, - "restrictions": { - "type": [ - "string", - "null" - ] - }, - "required_authorizations": { - "type": [ - "string", - "null" - ] - }, - "transfer_condition": { - "type": [ - "string", - "null" - ] - }, - "contract_type": { - "type": [ - "string", - "null" - ] - }, - "valid_from": { - "type": [ - "string", - "null" - ] - }, - "valid_until": { - "type": [ - "string", - "null" - ] - }, - "area": { - "type": [ - "string", - "null" - ] - }, - "area_unit": { - "type": [ - "string", - "null" - ] - }, - "amount": { - "type": [ - "string", - "null" - ] - }, - "currency": { - "type": [ - "string", - "null" - ] - }, - "value_kind": { - "type": [ - "string", - "null" - ] - }, - "participation_percent": { - "type": [ - "string", - "null" - ] - } - } - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } - }, - "/listings/{id}/objects/{object}": { - "put": { - "summary": "Update scoped object with immutable kind; amount requires currency and value kind; area requires unit", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "object", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" + "payload": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "maxLength": 200, + "description": "Proposed title" + }, + "description": { + "type": ["string", "null"], + "maxLength": 10000, + "description": "Summary: what is available, sought and key conditions" + }, + "declared_reason": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Reason and objective" + }, + "source_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Source reference" + }, + "valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Interest deadline (optional)" + }, + "proponent_type": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "organization", + "sole_trader", + "person", + "public_entity", + null + ], + "description": "Who is presenting the opportunity?" + }, + "proponent_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Name" + }, + "country": { + "type": ["string", "null"], + "maxLength": 2, + "description": "Country of origin or incorporation (two-letter ISO code)" + }, + "tax_identifier": { + "type": ["string", "null"], + "maxLength": 50, + "description": "NUIT or identifier (internal, when applicable)" + }, + "contact_name": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Contact person name" + }, + "contact_email": { + "type": ["string", "null"], + "maxLength": 254, + "description": "Email (provide email or phone)" + }, + "contact_phone": { + "type": ["string", "null"], + "maxLength": 40, + "description": "Phone (provide phone or email)" + }, + "contact_preference": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "through_portal", + "approved_business_contact", + null + ], + "description": "Contact preference" + }, + "acting_capacity": { + "type": ["string", "null"], + "maxLength": 30, + "enum": ["holder", "representative", null], + "description": "Acting capacity" + }, + "declared_holder": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Entity or person represented" + }, + "mandate_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Authorization document reference" + }, + "mandate_document_reference": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Internal authorization document reference (do not upload here)" + }, + "mandate_valid_until": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Mandate expiry" + }, + "disclosure_scope": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Limits of disclosure authorization" + }, + "legitimacy_declaration": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I declare authority to submit" + }, + "opportunity_type": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "investment_project", + "company_sale", + "asset_sale", + "business_partnership", + "financing_request", + "concession_opportunity", + "supply_opportunity", + null + ], + "description": "Opportunity type" + }, + "subsector_activity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Subsector and activity (by name)" + }, + "project_stage": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "idea", + "study", + "preparation", + "implementation", + "operating", + "suspended", + "other", + null + ], + "description": "Project or business status" + }, + "project_stage_other": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Specify the status" + }, + "availability": { + "type": ["string", "null"], + "maxLength": 40, + "enum": [ + "available", + "from_date", + "negotiating", + null + ], + "description": "Availability" + }, + "available_from": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Available from" + }, + "asking_value": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Value" + }, + "value_status": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "defined", + "indicative", + "consultation", + "unknown", + null + ], + "description": "Is the value defined?" + }, + "value_meaning": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking_price", + "investment_estimate", + "financing_sought", + null + ], + "description": "Meaning of the value" + }, + "value_nature": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "asking", + "indicative", + "appraised", + null + ], + "description": "Value basis" + }, + "currency": { + "type": ["string", "null"], + "maxLength": 3, + "description": "Currency (three-letter code)" + }, + "amount_secured": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Secured amount (projects)" + }, + "investment_total": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Estimated total investment (projects)" + }, + "amount_sought": { + "type": ["string", "null"], + "maxLength": 24, + "description": "Financing sought" + }, + "equity_percentage": { + "type": ["string", "null"], + "maxLength": 8, + "description": "Available equity (%)" + }, + "funds_use": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Use of funds" + }, + "asset_deal_kind": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "whole_business", + "participation", + "assets", + null + ], + "description": "Type of sale" + }, + "partnership_contribution": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "capital", + "technology", + "management", + "equipment", + "distribution", + "other", + null + ], + "description": "Contribution sought" + }, + "asset_modality": { + "type": ["string", "null"], + "maxLength": 30, + "enum": ["sale", "lease", "other", null], + "description": "Asset arrangement" + }, + "concession_entity": { + "type": ["string", "null"], + "maxLength": 300, + "description": "Concession authority" + }, + "concession_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official reference, procedure and deadline" + }, + "supply_description": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Good or service, quantity/capacity, destination and timing" + }, + "conditions": { + "type": ["string", "null"], + "maxLength": 4000, + "description": "Additional conditions" + }, + "geography_scope": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "specific", + "multiple", + "national", + "digital", + null + ], + "description": "Geographic scope" + }, + "province_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Province or Maputo City" + }, + "district_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "District" + }, + "admin_post_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Administrative post" + }, + "locality_id": { + "type": ["string", "null"], + "maxLength": 36, + "description": "Locality" + }, + "location_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Address or reference (internal)" + }, + "location_unknown": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", "no", null], + "description": "I do not know the exact location" + }, + "disclosure_precision": { + "type": ["string", "null"], + "maxLength": 30, + "enum": [ + "exact", + "approximate", + "administrative", + null + ], + "description": "Disclosure precision" + }, + "asset_notes": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Declared assets and rights summary" + }, + "source_provider": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Who supplied the information?" + }, + "source_date": { + "type": ["string", "null"], + "maxLength": 10, + "format": "date", + "description": "Source/information date" + }, + "official_source_entity": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Official source authority (if any)" + }, + "official_source_document": { + "type": ["string", "null"], + "maxLength": 500, + "description": "Related official document" + }, + "official_source_url": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Official source link" + }, + "official_source_period": { + "type": ["string", "null"], + "maxLength": 200, + "description": "Official source period" + }, + "document_reference": { + "type": ["string", "null"], + "maxLength": 2000, + "description": "Document references (not validated files)" + }, + "confirm_information": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm the information reflects what I know and identify estimates or pending items." + }, + "confirm_disclosure_authority": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I confirm I am authorized to submit the opportunity and disclose selected content." + }, + "confirm_no_publication_guarantee": { + "type": ["string", "null"], + "maxLength": 3, + "enum": ["yes", null], + "description": "I understand submission is not publication, official approval or an investment guarantee." + }, + "wizard_version": { + "type": ["string", "null"], + "enum": ["1", null] + }, + "wizard_step": { + "type": ["string", "null"], + "pattern": "^[1-8]$" + } + } }, - "version_no": { - "type": "integer" - }, - "verification_status": { - "const": "pending" + "sector_id": { + "type": ["integer", "null"], + "description": "Existing active public.sectors identifier; required on wizard submission." } - } } - } } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "version_no", - "reason", - "payload" - ], - "properties": { - "kind": { - "enum": [ - "company", - "participation", - "asset", - "property", - "unit", - "improvement", - "agricultural_investment", - "land_right", - "contract", - "license", - "authorization", - "encumbrance", - "valuation", - "capital_request" - ] - }, - "version_no": { - "type": "integer", - "minimum": 1 - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - }, - "payload": { - "type": "object", - "additionalProperties": false, - "required": [ - "label", - "source_reference" - ], - "properties": { - "label": { - "type": [ - "string", - "null" - ] - }, - "declared_holder": { - "type": [ - "string", - "null" - ] - }, - "document_reference": { - "type": [ - "string", - "null" - ] - }, - "source_reference": { - "type": [ - "string", - "null" - ] - }, - "competent_entity": { - "type": [ - "string", - "null" - ] - }, - "purpose": { - "type": [ - "string", - "null" - ] - }, - "location_description": { - "type": [ - "string", - "null" - ] - }, - "boundaries_description": { - "type": [ - "string", - "null" - ] - }, - "occupancy": { - "type": [ - "string", - "null" - ] - }, - "condition": { - "type": [ - "string", - "null" - ] - }, - "encumbrances": { - "type": [ - "string", - "null" - ] - }, - "restrictions": { - "type": [ - "string", - "null" - ] - }, - "required_authorizations": { - "type": [ - "string", - "null" - ] - }, - "transfer_condition": { - "type": [ - "string", - "null" - ] - }, - "contract_type": { - "type": [ - "string", - "null" - ] - }, - "valid_from": { - "type": [ - "string", - "null" - ] - }, - "valid_until": { - "type": [ - "string", - "null" - ] - }, - "area": { - "type": [ - "string", - "null" - ] - }, - "area_unit": { - "type": [ - "string", - "null" - ] - }, - "amount": { - "type": [ - "string", - "null" - ] - }, - "currency": { - "type": [ - "string", - "null" - ] - }, - "value_kind": { - "type": [ - "string", - "null" - ] - }, - "participation_percent": { - "type": [ - "string", - "null" - ] - } - } - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } - }, - "/listings/{id}/verifications": { - "post": { - "summary": "Record independent finding while in_review; immutable encrypted evidence, optimistic version and scoped object. Does not publish or certify the business.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Private response; Cache-Control private, no-store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Business" - } - } - } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Account, ownership or current reviewer/MFA policy rejected" - }, - "404": { - "description": "Module disabled or resource unavailable" - }, - "409": { - "description": "Stale version or invalid transition" - }, - "422": { - "description": "Validation error" - }, - "429": { - "description": "Rate limited" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "version_no", - "check", - "outcome", - "evidence_reference", - "note" - ], - "properties": { - "version_no": { - "type": "integer", - "minimum": 1 - }, - "object_id": { - "type": [ - "string", - "null" - ], - "format": "uuid" - }, - "check": { - "enum": [ - "identity", - "representation", - "declared_ownership", - "documentary_ownership", - "document_received", - "document_validated", - "independent_valuation", - "land_right_documented", - "license_confirmed", - "validity", - "legal_transfer", - "prior_authorization" - ] - }, - "outcome": { - "enum": [ - "confirmed", - "rejected", - "inconclusive", - "revoked" - ] - }, - "evidence_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "note": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "valid_until": { - "type": [ - "string", - "null" - ], - "format": "date", - "description": "Required for confirmed findings; must be today or later." - } - } - } - } - } - }, - "description": "Session authentication and Laravel CSRF/origin protection apply. Use X-XSRF-TOKEN for browser clients." - } - } - }, - "components": { - "securitySchemes": { - "session": { - "type": "apiKey", - "in": "cookie", - "name": "session_cookie", - "description": "Actual Laravel cookie name comes from deployment configuration; no bearer token support." - } - }, - "schemas": { - "Business": { - "type": "object", - "required": [ - "id", - "code", - "modality", - "status", - "classification", - "version_no", - "payload" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "code": { - "type": "string" - }, - "modality": { - "type": "string" - }, - "status": { - "enum": [ - "draft", - "submitted", - "in_review", - "needs_information", - "withdrawn" - ] - }, - "classification": { - "enum": [ - "internal", - "confidential", - "restricted" - ] - }, - "version_no": { - "type": "integer", - "minimum": 1 - }, - "payload": { - "type": "object", - "additionalProperties": false, - "required": [ - "title" - ], - "properties": { - "title": { - "type": "string", - "maxLength": 200, - "description": "Proposed title" - }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 10000, - "description": "Summary: what is available, sought and key conditions" - }, - "declared_reason": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Reason and objective" - }, - "source_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Source reference" - }, - "valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Interest deadline (optional)" - }, - "proponent_type": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "organization", - "sole_trader", - "person", - "public_entity", - null - ], - "description": "Who is presenting the opportunity?" - }, - "proponent_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Name" - }, - "country": { - "type": [ - "string", - "null" - ], - "maxLength": 2, - "description": "Country of origin or incorporation (two-letter ISO code)" - }, - "tax_identifier": { - "type": [ - "string", - "null" - ], - "maxLength": 50, - "description": "NUIT or identifier (internal, when applicable)" - }, - "contact_name": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Contact person name" - }, - "contact_email": { - "type": [ - "string", - "null" - ], - "maxLength": 254, - "description": "Email (provide email or phone)" - }, - "contact_phone": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "description": "Phone (provide phone or email)" - }, - "contact_preference": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "through_portal", - "approved_business_contact", - null - ], - "description": "Contact preference" - }, - "acting_capacity": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "holder", - "representative", - null - ], - "description": "Acting capacity" - }, - "declared_holder": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Entity or person represented" - }, - "mandate_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Authorization document reference" - }, - "mandate_document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Internal authorization document reference (do not upload here)" - }, - "mandate_valid_until": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Mandate expiry" - }, - "disclosure_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Limits of disclosure authorization" - }, - "legitimacy_declaration": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I declare authority to submit" - }, - "opportunity_type": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "investment_project", - "company_sale", - "asset_sale", - "business_partnership", - "financing_request", - "concession_opportunity", - "supply_opportunity", - null - ], - "description": "Opportunity type" - }, - "subsector_activity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Subsector and activity (by name)" - }, - "project_stage": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "idea", - "study", - "preparation", - "implementation", - "operating", - "suspended", - "other", - null - ], - "description": "Project or business status" - }, - "project_stage_other": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Specify the status" - }, - "availability": { - "type": [ - "string", - "null" - ], - "maxLength": 40, - "enum": [ - "available", - "from_date", - "negotiating", - null - ], - "description": "Availability" - }, - "available_from": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Available from" - }, - "asking_value": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Value" - }, - "value_status": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "defined", - "indicative", - "consultation", - "unknown", - null - ], - "description": "Is the value defined?" - }, - "value_meaning": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking_price", - "investment_estimate", - "financing_sought", - null - ], - "description": "Meaning of the value" - }, - "value_nature": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "asking", - "indicative", - "appraised", - null - ], - "description": "Value basis" - }, - "currency": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "description": "Currency (three-letter code)" - }, - "amount_secured": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Secured amount (projects)" - }, - "investment_total": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Estimated total investment (projects)" - }, - "amount_sought": { - "type": [ - "string", - "null" - ], - "maxLength": 24, - "description": "Financing sought" - }, - "equity_percentage": { - "type": [ - "string", - "null" - ], - "maxLength": 8, - "description": "Available equity (%)" - }, - "funds_use": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Use of funds" - }, - "asset_deal_kind": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "whole_business", - "participation", - "assets", - null - ], - "description": "Type of sale" - }, - "partnership_contribution": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "capital", - "technology", - "management", - "equipment", - "distribution", - "other", - null - ], - "description": "Contribution sought" - }, - "asset_modality": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "sale", - "lease", - "other", - null - ], - "description": "Asset arrangement" - }, - "concession_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 300, - "description": "Concession authority" - }, - "concession_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official reference, procedure and deadline" - }, - "supply_description": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Good or service, quantity/capacity, destination and timing" - }, - "conditions": { - "type": [ - "string", - "null" - ], - "maxLength": 4000, - "description": "Additional conditions" - }, - "geography_scope": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "specific", - "multiple", - "national", - "digital", - null - ], - "description": "Geographic scope" - }, - "province_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Province or Maputo City" - }, - "district_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "District" - }, - "admin_post_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Administrative post" - }, - "locality_id": { - "type": [ - "string", - "null" - ], - "maxLength": 36, - "description": "Locality" - }, - "location_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Address or reference (internal)" - }, - "location_unknown": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - "no", - null - ], - "description": "I do not know the exact location" - }, - "disclosure_precision": { - "type": [ - "string", - "null" - ], - "maxLength": 30, - "enum": [ - "exact", - "approximate", - "administrative", - null - ], - "description": "Disclosure precision" - }, - "asset_notes": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Declared assets and rights summary" - }, - "source_provider": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Who supplied the information?" - }, - "source_date": { - "type": [ - "string", - "null" - ], - "maxLength": 10, - "format": "date", - "description": "Source/information date" - }, - "official_source_entity": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Official source authority (if any)" - }, - "official_source_document": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Related official document" - }, - "official_source_url": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Official source link" - }, - "official_source_period": { - "type": [ - "string", - "null" - ], - "maxLength": 200, - "description": "Official source period" - }, - "document_reference": { - "type": [ - "string", - "null" - ], - "maxLength": 2000, - "description": "Document references (not validated files)" - }, - "confirm_information": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm the information reflects what I know and identify estimates or pending items." - }, - "confirm_disclosure_authority": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I confirm I am authorized to submit the opportunity and disclose selected content." - }, - "confirm_no_publication_guarantee": { - "type": [ - "string", - "null" - ], - "maxLength": 3, - "enum": [ - "yes", - null - ], - "description": "I understand submission is not publication, official approval or an investment guarantee." - }, - "wizard_version": { - "type": [ - "string", - "null" - ], - "enum": [ - "1", - null - ] - }, - "wizard_step": { - "type": [ - "string", - "null" - ], - "pattern": "^[1-8]$" - } - } - }, - "sector_id": { - "type": [ - "integer", - "null" - ], - "description": "Existing active public.sectors identifier; required on wizard submission." - } } - } } - } } diff --git a/docs/investmoz/existing-business/phase-1.md b/docs/investmoz/existing-business/phase-1.md index 1c9bc1b8..bb91ac52 100644 --- a/docs/investmoz/existing-business/phase-1.md +++ b/docs/investmoz/existing-business/phase-1.md @@ -20,12 +20,12 @@ erDiagram ## Dicionário implementado -| Tabela | Campos e invariantes | -| --- | --- | -| existing_businesses | UUID, code único permanente, source_system/legacy_id conjuntamente nulos ou presentes e únicos, owner_user_id obrigatório, organization_id opcional, investment_item_id opcional e único, modality, status exclusivamente draft, classification internal/confidential/restricted, version_no positivo, payload cifrado, timestamps e deleted_at | -| existing_business_objects | UUID, business_id, kind distinguindo company/participation/asset/property/unit/improvement/agricultural_investment/land_right/contract/license/authorization/encumbrance/valuation/capital_request, classification confidential/restricted, verification_status exclusivamente pending, payload cifrado, timestamps | -| existing_business_mandates | UUID, business_id, representative_user_id, document_id, valid_from/valid_until coerentes, verification_status exclusivamente pending, payload cifrado, timestamps. Documento anexado não comprova legitimidade. Nenhum acesso é concedido pelo mandato pendente. | -| existing_business_revisions | UUID, business_id/version_no únicos, actor_id, action, reason cifrada, snapshot cifrado, SHA-256 do snapshot e created_at. Trigger rejeita UPDATE/DELETE. | +| Tabela | Campos e invariantes | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| existing_businesses | UUID, code único permanente, source_system/legacy_id conjuntamente nulos ou presentes e únicos, owner_user_id obrigatório, organization_id opcional, investment_item_id opcional e único, modality, status exclusivamente draft, classification internal/confidential/restricted, version_no positivo, payload cifrado, timestamps e deleted_at | +| existing_business_objects | UUID, business_id, kind distinguindo company/participation/asset/property/unit/improvement/agricultural_investment/land_right/contract/license/authorization/encumbrance/valuation/capital_request, classification confidential/restricted, verification_status exclusivamente pending, payload cifrado, timestamps | +| existing_business_mandates | UUID, business_id, representative_user_id, document_id, valid_from/valid_until coerentes, verification_status exclusivamente pending, payload cifrado, timestamps. Documento anexado não comprova legitimidade. Nenhum acesso é concedido pelo mandato pendente. | +| existing_business_revisions | UUID, business_id/version_no únicos, actor_id, action, reason cifrada, snapshot cifrado, SHA-256 do snapshot e created_at. Trigger rejeita UPDATE/DELETE. | Modalidades implementadas como vocabulário técnico, sem declarar admissibilidade: venda de empresa/participação, aumento de capital, investidor financeiro, parceiro estratégico, comprador, cessão, franchise, venda/arrendamento de activo, unidade, joint venture, sucessão, recuperação, expansão, internacionalização, imobiliário, investimento agrícola, direito fundiário e licenças mineira/florestal. As modalidades não fazem aprovação jurídica automática. diff --git a/docs/investmoz/existing-business/validation.md b/docs/investmoz/existing-business/validation.md index 80ca3cef..6a405edc 100644 --- a/docs/investmoz/existing-business/validation.md +++ b/docs/investmoz/existing-business/validation.md @@ -35,13 +35,13 @@ O prompt não fornece uma sequência numerada RF-MNE; `traceability.csv` usa ide ## Gate -| Critério obrigatório da Fase 0 | Evidência | Resultado | -| --- | --- | --- | -| Dono do produto/autorização de desenvolvimento | Instrução explícita do utilizador | Confirmado para desenvolvimento | -| Política de publicação, termos e base legal | Não fornecidos/validados nesta sessão | Pendente | -| Equipa e mandatos de revisão | Apenas RACI proposto; sem nomeações | Pendente | -| Ambiente seguro homologado | Testes isolados aprovados; revisão de segurança/UAT pendente | Parcial | -| Carteira piloto e titulares confirmados | Nenhuma carteira MNE validada fornecida | Pendente | +| Critério obrigatório da Fase 0 | Evidência | Resultado | +| ---------------------------------------------- | ------------------------------------------------------------ | ------------------------------- | +| Dono do produto/autorização de desenvolvimento | Instrução explícita do utilizador | Confirmado para desenvolvimento | +| Política de publicação, termos e base legal | Não fornecidos/validados nesta sessão | Pendente | +| Equipa e mandatos de revisão | Apenas RACI proposto; sem nomeações | Pendente | +| Ambiente seguro homologado | Testes isolados aprovados; revisão de segurança/UAT pendente | Parcial | +| Carteira piloto e titulares confirmados | Nenhuma carteira MNE validada fornecida | Pendente | **No-Go para homologar o gate.** Artefactos de descoberta entregues; Fase 1 não iniciada, conforme regra explícita de paragem do prompt. Próxima fase proposta: modelo canónico e governação, com evidências e classificação por entidade. diff --git a/docs/investmoz/existing-business/wizard-browser-validation.json b/docs/investmoz/existing-business/wizard-browser-validation.json index 67cc673c..22e1722d 100644 --- a/docs/investmoz/existing-business/wizard-browser-validation.json +++ b/docs/investmoz/existing-business/wizard-browser-validation.json @@ -1,24 +1,24 @@ { - "passed": true, - "observations": [ - { - "scenario": "autosave_and_reload", - "passed": true - }, - { - "scenario": "responsive_summary", - "width": 390, - "passed": true - }, - { - "scenario": "responsive_summary", - "width": 1440, - "passed": true - }, - { - "scenario": "private_submission", - "passed": true - } - ], - "errors": [] + "passed": true, + "observations": [ + { + "scenario": "autosave_and_reload", + "passed": true + }, + { + "scenario": "responsive_summary", + "width": 390, + "passed": true + }, + { + "scenario": "responsive_summary", + "width": 1440, + "passed": true + }, + { + "scenario": "private_submission", + "passed": true + } + ], + "errors": [] } diff --git a/docs/investmoz/existing-business/wizard-implementation.md b/docs/investmoz/existing-business/wizard-implementation.md index 731a0c1b..a6d12982 100644 --- a/docs/investmoz/existing-business/wizard-implementation.md +++ b/docs/investmoz/existing-business/wizard-implementation.md @@ -20,16 +20,16 @@ 16. **Riscos residuais:** restantes campos e relações A; identidade normalizada; coordenadas/geometria; CRM MNE; scanner/upload; redis em operação; consentimentos separados; comprovativo/notificações; SLA; aprovações/publicação; tradução integral; testes de acessibilidade, carga e serviços externos. A conta de BD local ainda é superuser. Nenhuma alegação de prontidão para produção. 17. **Go/No-Go:** Go para testar preparação privada local; **No-Go para entrega integral A–C ou Go-Live**. Os fluxos incompletos não foram expostos como funcionais. -| Requisito | Implementação actual | Estado | -|---|---|---| -| A: proponente/representação | Campos cifrados, mandato condicional, declaração e validação | Parcial; sem verificação de identidade/documentos | -| A: modalidade/sector/valor | Taxonomia existente, FK, limites, moeda e natureza na submissão | Parcial; faltam intervalos e estruturas completas | -| A: localização/contexto | Descrição privada | Geografia estruturada e ligações pendentes | -| A: activos/direitos | Editor tipado existente; objecto aplicável exigido por modalidade | Parcial | -| A: fontes/documentos | Referências declaradas | Upload/scanner pendentes | -| A: rascunho/retoma/resumo | Autosave, versão, oito etapas, confirmação no servidor | Testado | -| B: interesse/CRM | Serviço geral existente preservado | Integração MNE pendente | -| C: revisão | Revisão privada e histórico existentes | Tarefas/SLA/dupla aprovação pendentes | +| Requisito | Implementação actual | Estado | +| --------------------------- | ----------------------------------------------------------------- | ------------------------------------------------- | +| A: proponente/representação | Campos cifrados, mandato condicional, declaração e validação | Parcial; sem verificação de identidade/documentos | +| A: modalidade/sector/valor | Taxonomia existente, FK, limites, moeda e natureza na submissão | Parcial; faltam intervalos e estruturas completas | +| A: localização/contexto | Descrição privada | Geografia estruturada e ligações pendentes | +| A: activos/direitos | Editor tipado existente; objecto aplicável exigido por modalidade | Parcial | +| A: fontes/documentos | Referências declaradas | Upload/scanner pendentes | +| A: rascunho/retoma/resumo | Autosave, versão, oito etapas, confirmação no servidor | Testado | +| B: interesse/CRM | Serviço geral existente preservado | Integração MNE pendente | +| C: revisão | Revisão privada e histórico existentes | Tarefas/SLA/dupla aprovação pendentes | ```powershell php artisan migrate --path=database/migrations/2026_09_29_200000_existing_business_form_sector.php diff --git a/docs/investmoz/gis/README.md b/docs/investmoz/gis/README.md index e374aacc..5eaa0d97 100644 --- a/docs/investmoz/gis/README.md +++ b/docs/investmoz/gis/README.md @@ -29,14 +29,14 @@ O dono do produto autorizou a progressão automática por todas as fases nesta s ## Estado por fase -| Fase | Estado e recomendação | Evidência / condição | -| --- | --- | --- | -| 0 — inventário | Parcial; Go condicionado para artefactos preparatórios | Código, índices, mapas e controlos inventariados abaixo. Excel localizado e inspeccionado após indicação do utilizador; documento original A.31 ainda não localizado. | -| 1 — modelo e regras | Proposta de reconciliação; Go condicionado | Reutilizar o cadastro existente, sem uma segunda base GIS. Reconciliar a proposta com A.31 antes de fechar o modelo. | -| 2 — serviços e importação | Auditor de XLSX implementado; restante pendente | Auditor local sem ligação à BD, com checksum, autorização de inspecção, limites e quarentena. Não é importador. | -| 3 — mapas e CRM | Base preexistente parcial; No-Go para declarar conformidade GIS | Catálogo e zonas têm mapas/listas; autorização geral, precisão e origem completa mapa→CRM ainda incompletas. | -| 4 — piloto e UAT | Testes sintéticos do auditor aprovados; No-Go para piloto real | Excel inspeccionado; faltam proveniência, classificação, mapeamento e amostras oficiais. | -| 5 — staging/produção | No-Go | Sem UAT, carga, matriz de mandatos, fontes e ensaio operacional do módulo completo. Nenhum deploy realizado. | +| Fase | Estado e recomendação | Evidência / condição | +| ------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 — inventário | Parcial; Go condicionado para artefactos preparatórios | Código, índices, mapas e controlos inventariados abaixo. Excel localizado e inspeccionado após indicação do utilizador; documento original A.31 ainda não localizado. | +| 1 — modelo e regras | Proposta de reconciliação; Go condicionado | Reutilizar o cadastro existente, sem uma segunda base GIS. Reconciliar a proposta com A.31 antes de fechar o modelo. | +| 2 — serviços e importação | Auditor de XLSX implementado; restante pendente | Auditor local sem ligação à BD, com checksum, autorização de inspecção, limites e quarentena. Não é importador. | +| 3 — mapas e CRM | Base preexistente parcial; No-Go para declarar conformidade GIS | Catálogo e zonas têm mapas/listas; autorização geral, precisão e origem completa mapa→CRM ainda incompletas. | +| 4 — piloto e UAT | Testes sintéticos do auditor aprovados; No-Go para piloto real | Excel inspeccionado; faltam proveniência, classificação, mapeamento e amostras oficiais. | +| 5 — staging/produção | No-Go | Sem UAT, carga, matriz de mandatos, fontes e ensaio operacional do módulo completo. Nenhum deploy realizado. | Não foram executadas migrations, importações, alterações de dados ou publicação de mapas nesta entrega GIS. O avanço técnico independente foi executado; as operações dependentes de fontes ausentes continuam pendentes, sem presumir os respectivos valores. @@ -58,18 +58,18 @@ Pesquisaram-se nomes de ficheiros DOCX/XLSX/PDF no repositório (incluindo ignor Os identificadores TECH abaixo são **provisórios locais**, não transcrição da A.31. Não se criou uma segunda sequência RF-GIS. -| RF-GEO | TECH provisório | Modelo / serviço previsto | API / evento previsto | Frontend e teste necessário | Estado | -| --- | --- | --- | --- | --- | --- | -| 001 | TECH-GEO-CAD | Adaptar geo_locations, proveniência, validade, revisão imutável | locations GET/POST/PATCH; location.revised | Cadastro; códigos duplicados, ciclos e concorrência | Base parcial | -| 002 | TECH-GEO-REL | Adaptar project_locations, papel, período, natureza | Relações explicitadas no contrato de localização | Vários projectos sem duplicar geometria | Base parcial | -| 003 | TECH-GEO-MULTI | Localização principal por período; secundárias | Ficha do projecto/localizações | Projecto multiterritorial; unicidade principal | Parcial | -| 004 | TECH-GEO-VALID | Geometria versionada, CRS original, método de transformação | geometries; validate; geometry.validated | Painel; SRID ausente, inválida, vazia e territorialmente divergente | PostGIS/zonas parcial | -| 005 | TECH-GEO-AUTH | geo_authorizations, documento, entidade e validade | authorize-publication; authorization.revoked | Painel de autorização; expiração/revogação em todos os caminhos | Geral em falta | -| 006 | TECH-GEO-PRECISION | Representação autorizada separada da geometria interna | Serviço comum a mapas, pesquisa, exportação e cache | Não reconstrução por bbox, raio, exportação ou respostas combinadas | Em falta | -| 007 | TECH-GEO-INSTITUTION | map_validation_requests, protocolo, anexos, decisões | validation.requested/decided | Painel interno; contestação impede publicação | Em falta | -| 008 | TECH-GEO-SUSPEND | Suspensão transaccional e tarefa de revalidação | suspend; map.suspended | Revogação imediata; cache e links históricos | Zonas parcial | -| 009 | TECH-GEO-SEARCH | GiST, filtros aplicados à representação autorizada | maps/search, projects, corridors, clusters, nearby | Lista sincronizada; limites, paginação, teclado e carga | Parcial | -| 010 | TECH-GEO-IMPORT | Batch, checksum, manifesto, relatório por linha, idempotência | import-jobs, errors, reconcile; import.reconciled | Upload privado; ZIP/XML hostil, duplicados e reconciliação | Auditor preparatório implementado | +| RF-GEO | TECH provisório | Modelo / serviço previsto | API / evento previsto | Frontend e teste necessário | Estado | +| ------ | -------------------- | --------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------- | +| 001 | TECH-GEO-CAD | Adaptar geo_locations, proveniência, validade, revisão imutável | locations GET/POST/PATCH; location.revised | Cadastro; códigos duplicados, ciclos e concorrência | Base parcial | +| 002 | TECH-GEO-REL | Adaptar project_locations, papel, período, natureza | Relações explicitadas no contrato de localização | Vários projectos sem duplicar geometria | Base parcial | +| 003 | TECH-GEO-MULTI | Localização principal por período; secundárias | Ficha do projecto/localizações | Projecto multiterritorial; unicidade principal | Parcial | +| 004 | TECH-GEO-VALID | Geometria versionada, CRS original, método de transformação | geometries; validate; geometry.validated | Painel; SRID ausente, inválida, vazia e territorialmente divergente | PostGIS/zonas parcial | +| 005 | TECH-GEO-AUTH | geo_authorizations, documento, entidade e validade | authorize-publication; authorization.revoked | Painel de autorização; expiração/revogação em todos os caminhos | Geral em falta | +| 006 | TECH-GEO-PRECISION | Representação autorizada separada da geometria interna | Serviço comum a mapas, pesquisa, exportação e cache | Não reconstrução por bbox, raio, exportação ou respostas combinadas | Em falta | +| 007 | TECH-GEO-INSTITUTION | map_validation_requests, protocolo, anexos, decisões | validation.requested/decided | Painel interno; contestação impede publicação | Em falta | +| 008 | TECH-GEO-SUSPEND | Suspensão transaccional e tarefa de revalidação | suspend; map.suspended | Revogação imediata; cache e links históricos | Zonas parcial | +| 009 | TECH-GEO-SEARCH | GiST, filtros aplicados à representação autorizada | maps/search, projects, corridors, clusters, nearby | Lista sincronizada; limites, paginação, teclado e carga | Parcial | +| 010 | TECH-GEO-IMPORT | Batch, checksum, manifesto, relatório por linha, idempotência | import-jobs, errors, reconcile; import.reconciled | Upload privado; ZIP/XML hostil, duplicados e reconciliação | Auditor preparatório implementado | ## Modelo, contratos e autorização propostos — Fase 1 diff --git a/docs/investmoz/gis/ine-import-status.md b/docs/investmoz/gis/ine-import-status.md index 155fea1e..bbadb7fc 100644 --- a/docs/investmoz/gis/ine-import-status.md +++ b/docs/investmoz/gis/ine-import-status.md @@ -51,18 +51,18 @@ Relatórios indicam ordinal do registo CSV e códigos de erro, sem repetir o con Validação executada: suite PHP completa com **168 testes e 1.707 verificações aprovadas** (32,524 s), incluindo seis novos testes unitários de CSV; PHPStan sem erros e Pint aprovado nos ficheiros PHP alterados. A extracção do PDF real terminou com 666 linhas de revisão. Não houve alteração de frontend nem alegação de testes visuais nesta fase. -| Critério | Situação e motivo | Impacto / próxima acção | -| --- | --- | --- | -| Original, checksum e revisão reproduzível | Executado | Conservar pacote privado | -| Auditoria estrutural CSV | Executada | Não substitui carga ou aprovação | -| Seed MZ, 11 províncias e capitais em staging | Por implementar | Criar seed idempotente com proveniência, sem localidades fictícias | -| Batch persistente e carga atómica | Por implementar | Preservar rejeições e impedir alteração silenciosa de dados aprovados | -| CSV final extraído do PDF | Colunas ambíguas | Reconciliar visualmente e validar antes de converter | -| Vigência, licença e totais oficiais | Não comprovados | Obter evidência documental antes da publicação | -| Selectores em cascata e consumidores | Por implementar | Criar componente acessível e validar UUID/hierarquia no servidor | -| Atribuições FK, versões de limites e serviços espaciais | Por implementar | Completar schema e serviços sem duplicar cadastro | -| OIDC, OpenAPI integral, p95 e carga | Por implementar | Completar integração e ensaios; sessão local continua com limites documentados | -| UI editorial, acessibilidade e UAT | Por implementar | Entregar e testar jornadas no navegador | -| Go-live integral | Não concluído | Resolver itens anteriores e verificar critérios do anexo | +| Critério | Situação e motivo | Impacto / próxima acção | +| ------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------ | +| Original, checksum e revisão reproduzível | Executado | Conservar pacote privado | +| Auditoria estrutural CSV | Executada | Não substitui carga ou aprovação | +| Seed MZ, 11 províncias e capitais em staging | Por implementar | Criar seed idempotente com proveniência, sem localidades fictícias | +| Batch persistente e carga atómica | Por implementar | Preservar rejeições e impedir alteração silenciosa de dados aprovados | +| CSV final extraído do PDF | Colunas ambíguas | Reconciliar visualmente e validar antes de converter | +| Vigência, licença e totais oficiais | Não comprovados | Obter evidência documental antes da publicação | +| Selectores em cascata e consumidores | Por implementar | Criar componente acessível e validar UUID/hierarquia no servidor | +| Atribuições FK, versões de limites e serviços espaciais | Por implementar | Completar schema e serviços sem duplicar cadastro | +| OIDC, OpenAPI integral, p95 e carga | Por implementar | Completar integração e ensaios; sessão local continua com limites documentados | +| UI editorial, acessibilidade e UAT | Por implementar | Entregar e testar jornadas no navegador | +| Go-live integral | Não concluído | Resolver itens anteriores e verificar critérios do anexo | Nenhuma migration ou novo endpoint neste incremento; nenhum território importado, publicado ou alterado. Reversão: retirar os componentes acrescentados, preservando os pacotes de auditoria; não há rollback de dados. O núcleo territorial anterior está documentado em [territorial-mvp.md](territorial-mvp.md). Os itens pendentes são trabalho restante, não novos pedidos de autorização. diff --git a/docs/investmoz/gis/opportunity-dropdown-browser.json b/docs/investmoz/gis/opportunity-dropdown-browser.json index 6f9048a3..f0dab1cd 100644 --- a/docs/investmoz/gis/opportunity-dropdown-browser.json +++ b/docs/investmoz/gis/opportunity-dropdown-browser.json @@ -1,95 +1,95 @@ { - "date": "2026-09-29T11:28:43.868Z", - "observations": [ - "create_save_reopen_full_path", - "validation_preserves_selection", - "edit_save_reopen_and_descendant_reset", - "network_failure_and_retry", - "empty_search", - "rapid_parent_change_cancels_and_clears_children" - ], - "requests": [ - { - "method": "POST", - "path": "/management/opportunities", - "payload": { - "province_id": "10000000-0000-4000-8000-000000000002", - "district_id": "10000000-0000-4000-8000-000000000003", - "admin_post_id": "10000000-0000-4000-8000-000000000004", - "locality_id": "10000000-0000-4000-8000-000000000005", - "territory_version": "0", - "reference": "geo-browser-1790681317445" - } - }, - { - "method": "PUT", - "path": "/management/opportunities/53", - "payload": { - "province_id": "10000000-0000-4000-8000-000000000002", - "district_id": "10000000-0000-4000-8000-000000000003", - "admin_post_id": "10000000-0000-4000-8000-000000000004", - "locality_id": "10000000-0000-4000-8000-000000000005", - "territory_version": "1", - "reference": "" - } - }, - { - "method": "PUT", - "path": "/management/opportunities/53", - "payload": { - "province_id": "10000000-0000-4000-8000-000000000002", - "district_id": "10000000-0000-4000-8000-000000000003", - "admin_post_id": "", - "locality_id": "", - "territory_version": "1", - "reference": "geo-browser-1790681317445" - } - } - ], - "responses": [ - { - "path": "/management/opportunities/create", - "status": 200 - }, - { - "path": "/management/opportunities", - "status": 200 - }, - { - "path": "/management/opportunities/53/edit", - "status": 200 - }, - { - "path": "/management/opportunities/53/coordinates", - "status": 200 - }, - { - "path": "/management/opportunities/53/edit", - "status": 200 - }, - { - "path": "/management/opportunities", - "status": 200 - }, - { - "path": "/management/opportunities/53/edit", - "status": 200 - }, - { - "path": "/management/opportunities/53/coordinates", - "status": 200 - } - ], - "widths": [ - { - "width": 390, - "noOverflow": true - }, - { - "width": 1440, - "noOverflow": true - } - ], - "javascript_errors": 0, - "scope": "Synthetic isolated QA" + "date": "2026-09-29T11:28:43.868Z", + "observations": [ + "create_save_reopen_full_path", + "validation_preserves_selection", + "edit_save_reopen_and_descendant_reset", + "network_failure_and_retry", + "empty_search", + "rapid_parent_change_cancels_and_clears_children" + ], + "requests": [ + { + "method": "POST", + "path": "/management/opportunities", + "payload": { + "province_id": "10000000-0000-4000-8000-000000000002", + "district_id": "10000000-0000-4000-8000-000000000003", + "admin_post_id": "10000000-0000-4000-8000-000000000004", + "locality_id": "10000000-0000-4000-8000-000000000005", + "territory_version": "0", + "reference": "geo-browser-1790681317445" + } + }, + { + "method": "PUT", + "path": "/management/opportunities/53", + "payload": { + "province_id": "10000000-0000-4000-8000-000000000002", + "district_id": "10000000-0000-4000-8000-000000000003", + "admin_post_id": "10000000-0000-4000-8000-000000000004", + "locality_id": "10000000-0000-4000-8000-000000000005", + "territory_version": "1", + "reference": "" + } + }, + { + "method": "PUT", + "path": "/management/opportunities/53", + "payload": { + "province_id": "10000000-0000-4000-8000-000000000002", + "district_id": "10000000-0000-4000-8000-000000000003", + "admin_post_id": "", + "locality_id": "", + "territory_version": "1", + "reference": "geo-browser-1790681317445" + } + } + ], + "responses": [ + { + "path": "/management/opportunities/create", + "status": 200 + }, + { + "path": "/management/opportunities", + "status": 200 + }, + { + "path": "/management/opportunities/53/edit", + "status": 200 + }, + { + "path": "/management/opportunities/53/coordinates", + "status": 200 + }, + { + "path": "/management/opportunities/53/edit", + "status": 200 + }, + { + "path": "/management/opportunities", + "status": 200 + }, + { + "path": "/management/opportunities/53/edit", + "status": 200 + }, + { + "path": "/management/opportunities/53/coordinates", + "status": 200 + } + ], + "widths": [ + { + "width": 390, + "noOverflow": true + }, + { + "width": 1440, + "noOverflow": true + } + ], + "javascript_errors": 0, + "scope": "Synthetic isolated QA" } diff --git a/docs/investmoz/gis/source-manifest.example.json b/docs/investmoz/gis/source-manifest.example.json index d0d2e004..b66272b2 100644 --- a/docs/investmoz/gis/source-manifest.example.json +++ b/docs/investmoz/gis/source-manifest.example.json @@ -1,11 +1,11 @@ { - "sha256": null, - "owner": null, - "version": null, - "received_at": null, - "classification": null, - "inspection_authorization": null, - "original_crs": null, - "publication_authorization": null, - "column_mapping": null + "sha256": null, + "owner": null, + "version": null, + "received_at": null, + "classification": null, + "inspection_authorization": null, + "original_crs": null, + "publication_authorization": null, + "column_mapping": null } diff --git a/docs/investmoz/gis/sql-workbook-inspection.json b/docs/investmoz/gis/sql-workbook-inspection.json index 96d6bd2c..0a0a6b8a 100644 --- a/docs/investmoz/gis/sql-workbook-inspection.json +++ b/docs/investmoz/gis/sql-workbook-inspection.json @@ -1,18 +1,18 @@ { - "source_sha256": "1e36c769c4767420b10f1ff5ee0c2955cdb1b03c98179c5cad549e832ffb1f15", - "ddl_cells": "SQL_DDL!A1:A14", - "insert_cells": "SQL_INSERTS!B2:B21415", - "counts": { - "provincia": 11, - "distrito": 150, - "posto_administrativo": 451, - "localidade": 1377, - "bairro_urbano": 2838, - "aldeia_quarteirao": 16583 - }, - "total": 21410, - "auxiliary_parents_from_source": 1075, - "quality_rows": 1147, - "has_coordinates": false, - "warning": "1075 parents explicitly marked NAO INFORMADO by the workbook; these are unresolved references, not authenticated administrative designations." + "source_sha256": "1e36c769c4767420b10f1ff5ee0c2955cdb1b03c98179c5cad549e832ffb1f15", + "ddl_cells": "SQL_DDL!A1:A14", + "insert_cells": "SQL_INSERTS!B2:B21415", + "counts": { + "provincia": 11, + "distrito": 150, + "posto_administrativo": 451, + "localidade": 1377, + "bairro_urbano": 2838, + "aldeia_quarteirao": 16583 + }, + "total": 21410, + "auxiliary_parents_from_source": 1075, + "quality_rows": 1147, + "has_coordinates": false, + "warning": "1075 parents explicitly marked NAO INFORMADO by the workbook; these are unresolved references, not authenticated administrative designations." } diff --git a/docs/investmoz/gis/territorial-mvp.md b/docs/investmoz/gis/territorial-mvp.md index 33e29c69..2edf4505 100644 --- a/docs/investmoz/gis/territorial-mvp.md +++ b/docs/investmoz/gis/territorial-mvp.md @@ -12,13 +12,13 @@ O índice legado de código/tipo é global, mais restritivo que código de prov Migration `2026_09_28_150000_territorial_registry.php` acrescenta cinco tabelas: -| Tabela | Finalidade e controlos | -| --- | --- | -| geo_sources | Fonte, entidade, referência, licença, checksum, versão, data, CRS original e estado; checksum/versão únicos; editor/revisor e evidência da decisão | -| geo_territory_profiles | FK única para geo_locations; fonte, ISO2/ISO3 opcionais únicos, capital por FK, vigência, estado, editor/revisor/publicador | -| geo_names | Nomes/idiomas por FK; nome normalizado e oficial; unicidade por entidade/idioma/nome e um oficial por idioma | -| geo_territory_revisions | Snapshot e motivo cifrados, actor, versão e checksum; UPDATE/DELETE recusados pelo trigger | -| geo_write_receipts | Actor, chave UUID, escopo, hash do pedido e resposta cifrada; unicidade e serialização contra duplicação concorrente | +| Tabela | Finalidade e controlos | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| geo_sources | Fonte, entidade, referência, licença, checksum, versão, data, CRS original e estado; checksum/versão únicos; editor/revisor e evidência da decisão | +| geo_territory_profiles | FK única para geo_locations; fonte, ISO2/ISO3 opcionais únicos, capital por FK, vigência, estado, editor/revisor/publicador | +| geo_names | Nomes/idiomas por FK; nome normalizado e oficial; unicidade por entidade/idioma/nome e um oficial por idioma | +| geo_territory_revisions | Snapshot e motivo cifrados, actor, versão e checksum; UPDATE/DELETE recusados pelo trigger | +| geo_write_receipts | Actor, chave UUID, escopo, hash do pedido e resposta cifrada; unicidade e serialização contra duplicação concorrente | Hierarquia validada no serviço: país sem pai, província→país, distrito→província, posto→distrito e localidade→posto. Tipo é imutável; pai apagado, salto de nível e código/nome duplicado sob o mesmo pai são rejeitados. Alterações são serializadas, com versão esperada, snapshot e invalidação de publicação. Excepções de localidades sem posto ainda não têm workflow; não se cria pai fictício. diff --git a/docs/investmoz/gis/territorial-openapi.json b/docs/investmoz/gis/territorial-openapi.json index 6f8ac08b..794c4692 100644 --- a/docs/investmoz/gis/territorial-openapi.json +++ b/docs/investmoz/gis/territorial-openapi.json @@ -1,1191 +1,1169 @@ { - "openapi": "3.1.0", - "info": { - "title": "InvestMoz territorial registry — implemented subset", - "version": "0.1.0", - "description": "Local session adapter. OAuth/OIDC, geometric queries, assignments, imports, hierarchy endpoint and administrative UI remain unimplemented. Response schemas require expansion before external certification." - }, - "servers": [ - { - "url": "/api/v1/geo" + "openapi": "3.1.0", + "info": { + "title": "InvestMoz territorial registry — implemented subset", + "version": "0.1.0", + "description": "Local session adapter. OAuth/OIDC, geometric queries, assignments, imports, hierarchy endpoint and administrative UI remain unimplemented. Response schemas require expansion before external certification." + }, + "servers": [ + { + "url": "/api/v1/geo" + } + ], + "paths": { + "/countries": { + "get": { + "summary": "Read currently published territories: countries", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/provinces": { + "get": { + "summary": "Read currently published territories: provinces", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/districts": { + "get": { + "summary": "Read currently published territories: districts", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/admin-posts": { + "get": { + "summary": "Read currently published territories: admin-posts", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/localities": { + "get": { + "summary": "Read currently published territories: localities", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/search": { + "get": { + "summary": "Read currently published territories: search", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "parent_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/territories/{type}/{id}": { + "get": { + "summary": "Read published territory and translated names", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "type", + "in": "path", + "required": true, + "schema": { + "enum": [ + "country", + "province", + "district", + "admin_post", + "locality" + ] + } + } + ], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/territories": { + "post": { + "summary": "Create private territorial draft; designated editor with MFA", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "location_type": { + "enum": [ + "country", + "province", + "district", + "admin_post", + "locality" + ] + }, + "parent_id": { + "type": ["string", "null"], + "format": "uuid" + }, + "source_id": { + "type": "string", + "format": "uuid" + }, + "valid_from": { + "type": "string", + "format": "date" + }, + "valid_to": { + "type": ["string", "null"], + "format": "date" + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + } + }, + "required": [ + "name", + "code", + "location_type", + "source_id", + "valid_from", + "reason" + ] + } + } + } + }, + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/territories/{id}": { + "patch": { + "summary": "Replace draft attributes and invalidate editorial approvals", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "location_type": { + "enum": [ + "country", + "province", + "district", + "admin_post", + "locality" + ] + }, + "parent_id": { + "type": ["string", "null"], + "format": "uuid" + }, + "source_id": { + "type": "string", + "format": "uuid" + }, + "valid_from": { + "type": "string", + "format": "date" + }, + "valid_to": { + "type": ["string", "null"], + "format": "date" + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 1000 + }, + "version_no": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "name", + "code", + "location_type", + "source_id", + "valid_from", + "reason", + "version_no" + ] + } + } + } + }, + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/territories/{id}/transitions": { + "post": { + "summary": "Submit, review, publish, reject or archive; independent actors required", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "enum": [ + "pending_review", + "approved", + "published", + "rejected", + "archived" + ] + }, + "version_no": { + "type": "integer", + "minimum": 1 + }, + "reason": { + "type": "string", + "minLength": 10 + } + }, + "required": ["action", "version_no", "reason"] + } + } + } + }, + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/sources": { + "post": { + "summary": "Create source in pending state", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "responsible_entity": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "license": { + "type": "string" + }, + "checksum": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_version": { + "type": "string" + }, + "collected_at": { + "type": "string", + "format": "date" + }, + "original_crs": { + "type": "string" + } + }, + "required": [ + "name", + "reference", + "checksum", + "source_version", + "collected_at" + ] + } + } + } + }, + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "201": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + }, + "get": { + "summary": "Management-only source metadata and decision evidence, MFA required", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/sources/{id}/transitions": { + "post": { + "summary": "Independent source approval or revocation with evidence reference", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "enum": ["approved", "revoked"] + }, + "approval_reference": { + "type": "string", + "minLength": 10 + } + }, + "required": ["action", "approval_reference"] + } + } + } + }, + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + }, + "/drafts": { + "get": { + "summary": "Management-only territorial editorial records, MFA required", + "security": [ + { + "laravelSession": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "JSON result with X-Correlation-ID; private, no-store" + }, + "401": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "403": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "404": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "409": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "422": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + }, + "429": { + "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" + } + } + } + } + }, + "components": { + "securitySchemes": { + "laravelSession": { + "type": "apiKey", + "in": "cookie", + "name": "deployment_session_cookie", + "description": "Actual name configured by Laravel; write requests also require Laravel CSRF/origin protection. This is not OAuth/OIDC." + } + } } - ], - "paths": { - "/countries": { - "get": { - "summary": "Read currently published territories: countries", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/provinces": { - "get": { - "summary": "Read currently published territories: provinces", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/districts": { - "get": { - "summary": "Read currently published territories: districts", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/admin-posts": { - "get": { - "summary": "Read currently published territories: admin-posts", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/localities": { - "get": { - "summary": "Read currently published territories: localities", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/search": { - "get": { - "summary": "Read currently published territories: search", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "parent_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "country_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/territories/{type}/{id}": { - "get": { - "summary": "Read published territory and translated names", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "type", - "in": "path", - "required": true, - "schema": { - "enum": [ - "country", - "province", - "district", - "admin_post", - "locality" - ] - } - } - ], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/territories": { - "post": { - "summary": "Create private territorial draft; designated editor with MFA", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string" - }, - "location_type": { - "enum": [ - "country", - "province", - "district", - "admin_post", - "locality" - ] - }, - "parent_id": { - "type": [ - "string", - "null" - ], - "format": "uuid" - }, - "source_id": { - "type": "string", - "format": "uuid" - }, - "valid_from": { - "type": "string", - "format": "date" - }, - "valid_to": { - "type": [ - "string", - "null" - ], - "format": "date" - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - } - }, - "required": [ - "name", - "code", - "location_type", - "source_id", - "valid_from", - "reason" - ] - } - } - } - }, - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/territories/{id}": { - "patch": { - "summary": "Replace draft attributes and invalidate editorial approvals", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string" - }, - "location_type": { - "enum": [ - "country", - "province", - "district", - "admin_post", - "locality" - ] - }, - "parent_id": { - "type": [ - "string", - "null" - ], - "format": "uuid" - }, - "source_id": { - "type": "string", - "format": "uuid" - }, - "valid_from": { - "type": "string", - "format": "date" - }, - "valid_to": { - "type": [ - "string", - "null" - ], - "format": "date" - }, - "reason": { - "type": "string", - "minLength": 10, - "maxLength": 1000 - }, - "version_no": { - "type": "integer", - "minimum": 1 - } - }, - "required": [ - "name", - "code", - "location_type", - "source_id", - "valid_from", - "reason", - "version_no" - ] - } - } - } - }, - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/territories/{id}/transitions": { - "post": { - "summary": "Submit, review, publish, reject or archive; independent actors required", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "enum": [ - "pending_review", - "approved", - "published", - "rejected", - "archived" - ] - }, - "version_no": { - "type": "integer", - "minimum": 1 - }, - "reason": { - "type": "string", - "minLength": 10 - } - }, - "required": [ - "action", - "version_no", - "reason" - ] - } - } - } - }, - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/sources": { - "post": { - "summary": "Create source in pending state", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "responsible_entity": { - "type": "string" - }, - "reference": { - "type": "string" - }, - "license": { - "type": "string" - }, - "checksum": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "source_version": { - "type": "string" - }, - "collected_at": { - "type": "string", - "format": "date" - }, - "original_crs": { - "type": "string" - } - }, - "required": [ - "name", - "reference", - "checksum", - "source_version", - "collected_at" - ] - } - } - } - }, - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "201": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - }, - "get": { - "summary": "Management-only source metadata and decision evidence, MFA required", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/sources/{id}/transitions": { - "post": { - "summary": "Independent source approval or revocation with evidence reference", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "enum": [ - "approved", - "revoked" - ] - }, - "approval_reference": { - "type": "string", - "minLength": 10 - } - }, - "required": [ - "action", - "approval_reference" - ] - } - } - } - }, - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - }, - "/drafts": { - "get": { - "summary": "Management-only territorial editorial records, MFA required", - "security": [ - { - "laravelSession": [] - } - ], - "parameters": [], - "responses": { - "200": { - "description": "JSON result with X-Correlation-ID; private, no-store" - }, - "401": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "403": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "404": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "409": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "422": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - }, - "429": { - "description": "Request rejected; validation and workflow errors use Problem Details within territorial middleware" - } - } - } - } - }, - "components": { - "securitySchemes": { - "laravelSession": { - "type": "apiKey", - "in": "cookie", - "name": "deployment_session_cookie", - "description": "Actual name configured by Laravel; write requests also require Laravel CSRF/origin protection. This is not OAuth/OIDC." - } - } - } } diff --git a/docs/investmoz/integration-repair-2026-10-04.md b/docs/investmoz/integration-repair-2026-10-04.md index c0b33543..3ac66b05 100644 --- a/docs/investmoz/integration-repair-2026-10-04.md +++ b/docs/investmoz/integration-repair-2026-10-04.md @@ -31,15 +31,15 @@ A página agora oferece os percursos “Tenho um projecto definido” e “Tenho ## Evidência - Browser Chromium (Playwright), desktop e viewport móvel de 390 × 844: - - `/oportunidades`: HTTP 200, 6 oportunidades publicadas no ambiente; - - `/carteira?kind=cluster`: HTTP 200, 8 dossiers, abertura de ficha sem erro SQL; - - filtros legados `/clusters`, `/corredores` e `/projectos-ancora` redireccionam correctamente; a selecção de uma província aplicou o filtro. A província escolhida não tinha dossiers de cluster publicados e mostrou o estado vazio; - - pesquisa deliberadamente sem correspondências: estado vazio útil; - - `/carteira?kind=project`: HTTP 200, 15 dossiers; a ficha informa quando o estado não está confirmado e não sugere concurso; - - `/zonas?category=zee`: HTTP 200, 0 registos publicados, sem dados demonstrativos; - - `/negocios`: HTTP 200, 0 anúncios, sem erro React após corrigir a paginação; - - `/como-investir`: ambos os percursos, selecção da ideia e contexto de actividade renderizam; - - menu móvel expande, foco por teclado avança e não foram observados erros JavaScript. + - `/oportunidades`: HTTP 200, 6 oportunidades publicadas no ambiente; + - `/carteira?kind=cluster`: HTTP 200, 8 dossiers, abertura de ficha sem erro SQL; + - filtros legados `/clusters`, `/corredores` e `/projectos-ancora` redireccionam correctamente; a selecção de uma província aplicou o filtro. A província escolhida não tinha dossiers de cluster publicados e mostrou o estado vazio; + - pesquisa deliberadamente sem correspondências: estado vazio útil; + - `/carteira?kind=project`: HTTP 200, 15 dossiers; a ficha informa quando o estado não está confirmado e não sugere concurso; + - `/zonas?category=zee`: HTTP 200, 0 registos publicados, sem dados demonstrativos; + - `/negocios`: HTTP 200, 0 anúncios, sem erro React após corrigir a paginação; + - `/como-investir`: ambos os percursos, selecção da ideia e contexto de actividade renderizam; + - menu móvel expande, foco por teclado avança e não foram observados erros JavaScript. - Evidência automatizada: [JSON do browser](pilot/investmoz-catalogue-guide-browser-2026-10-04.json) e [captura móvel](pilot/investmoz-guide-mobile-2026-10-04.png). - `npm.cmd run types:check`: passou. - `npm.cmd run build`: passou. Avisos: dependência opcional `fontaine` ausente e chunk `services` acima de 500 kB. @@ -57,6 +57,7 @@ A página agora oferece os percursos “Tenho um projecto definido” e “Tenho - No catálogo real carregado, há seis oportunidades provenientes do `OpportunitySeeder`, todas sem `last_validated_at`, e zero anúncios de negócio. Filtros por Nampula e agronegócio devolveram um cartão cada; o CTA anónimo encaminha para login. A origem seed não prova autorização comercial. O catálogo e detalhe agora avisam quando a data de validação documental não está registada. Evidência: [JSON](pilot/authorized-opportunity-filter-browser-2026-10-04.json). - `npm.cmd run types:check`, `npm.cmd run build` e `node --test tests/frontend/*.test.mjs`: passaram (139/139). Avisos de build: dependência opcional `fontaine` ausente e chunk `services` acima de 500 kB. - Os resultados e decisões de fonte para as 50 relações, 15 projectos âncora e catálogos constam em [revisão documental](catalogue-review-2026-10-04.md). + ## Pendências e limites 1. **50 relações candidatas:** falta atribuir revisor interno e registar fonte/evidência individual com vigência. A decisão pendente é a natureza de cada ligação (oficial, influência, proximidade, potencial, histórica) ou remoção. Não se exige certificador externo; relações sem prova ficam fora de pesquisas oficiais. Os catálogos e fichas continuam acessíveis. @@ -65,6 +66,7 @@ A página agora oferece os percursos “Tenho um projecto definido” e “Tenho 4. **Negócios e oportunidades:** os seis cartões disponíveis são seeds sem data de validação; o catálogo de negócios tem 0 anúncios. Falta ao menos um registo autorizado com fonte e data para verificar anúncio e pretensão real. O catálogo continua consultável com aviso; não há confirmação de oferta. 5. **ZFE:** a sigla não tem definição/fonte encontrada; pedir a referência original antes de criar categoria. 6. **Dispositivo físico:** falta ensaio num telemóvel físico; viewport Chromium não substitui compatibilidade real. + ## Diff lógico nesta entrega - `database/migrations/2026_10_03_180000_govern_investment_structure_relations.php`: migration aditiva pendente executada nas três tabelas; não foi editada. @@ -85,4 +87,4 @@ A página agora oferece os percursos “Tenho um projecto definido” e “Tenho ## Gate -**Proposta: pendente com decisões delimitadas, sem bloquear o portal globalmente.** A associação privada passou no teste browser autenticado e no teste PHP. As decisões humanas em falta são: rever os 50 vínculos com fonte/evidência; confirmar documentalmente os 15 projectos; fornecer fonte legal e registo real de zona; fornecer anúncios autorizados para ensaiar uma pretensão real; esclarecer ZFE; realizar ensaio num telemóvel físico. Os percursos públicos já verificados continuam disponíveis com os avisos registados. \ No newline at end of file +**Proposta: pendente com decisões delimitadas, sem bloquear o portal globalmente.** A associação privada passou no teste browser autenticado e no teste PHP. As decisões humanas em falta são: rever os 50 vínculos com fonte/evidência; confirmar documentalmente os 15 projectos; fornecer fonte legal e registo real de zona; fornecer anúncios autorizados para ensaiar uma pretensão real; esclarecer ZFE; realizar ensaio num telemóvel físico. Os percursos públicos já verificados continuam disponíveis com os avisos registados. diff --git a/docs/investmoz/land-assistance/legal-source-review-2026-10-03.md b/docs/investmoz/land-assistance/legal-source-review-2026-10-03.md index 81fad3bf..94dac586 100644 --- a/docs/investmoz/land-assistance/legal-source-review-2026-10-03.md +++ b/docs/investmoz/land-assistance/legal-source-review-2026-10-03.md @@ -9,17 +9,17 @@ O acervo disponível permite identificar diplomas e pontos de revisão, mas não ## Matriz de fontes e efeitos -| Matéria | Diploma ou indicação | Fonte consultada e qualidade | Vigência/estado nesta triagem | Área do produto | Teste/controlo | Aprovação | -| --- | --- | --- | --- | --- | --- | --- | -| Lei de Terras | Lei n.º 19/97, de 1 de Outubro; a reprodução consultada contém, entre outros, arts. 12–15 (aquisição, titulação, registo e prova), art. 19 (plano de exploração) e art. 24 (participação comunitária) | PDF “Lei de terra” na página normativa do PDUL, programa governamental. É reprodução útil para localizar dispositivos, ainda sem confronto página a página com o BR | Lei-base histórica identificada. Consolidação/alterações e vigência artigo a artigo por confirmar no BR e com revisão jurídica | Avisos, categorias de pedido, direitos adquiridos por ocupação e formulação de campos | Testar que ausência de título/registo não é tratada pelo portal como inexistência de direito; nenhuma inferência jurídica a partir de mapa | Pendente de confirmação por jurista fundiário | -| Regulamento rural | Decreto n.º 66/98, de 8 de Dezembro | PDF “Regulamento da Lei de Terra” na página normativa do PDUL; reprodução governamental. Fontes secundárias consultadas indicam alterações pelos Decretos n.º 1/2003, 50/2007 e 43/2010; falta conferir os diplomas e artigos no BR | Texto-base identificado; consolidação e vigência por dispositivo pendentes | Checklist por modalidade, consulta comunitária, plano e processo rural | Não tornar campos ou scans em obrigação até completar a matriz artigo→campo→evidência | Pendente | -| Solo urbano | Decreto n.º 60/2006, de 26 de Dezembro, Regulamento do Solo Urbano | PDF ligado pela página normativa do PDUL; reprodução governamental | Identificado; relação com actos posteriores, regulamentos locais e reformas de ordenamento a validar por jurisdição | Ramificação urbana distinta; não reutilizar automaticamente procedimentos rurais | Caso de teste urbano não deve herdar checklist rural por omissão | Pendente | -| Taxas fundiárias | Diploma Ministerial n.º 47/2020; o sumário do BR n.º 176 (14-09-2020) diz que actualiza taxas do Decreto 66/98 e Decreto 77/99 e revoga o DM n.º 144/2010 | Sumário da Imprensa Nacional de Moçambique (fonte oficial), sem o texto integral/quadros revistos nesta revisão | Existência da actualização confirmada pelo sumário; valor, moeda, âmbito, aplicação temporal e eventual alteração posterior não confirmados | Biblioteca informativa; não calcular nem pedir pagamento no formulário | As taxas não podem ter fallback demonstrativo ou valor de 2010; assegurar ausência de cálculo activo até fonte integral aprovada | Pendente | -| Proposta de nova Lei de Terras | Proposta de Revisão da Lei de Terras, com PDF “Lei de Terras” na página do Parlamento | Parlamento de Moçambique lista a matéria na secção “Projectos e Propostas de Lei”; comunicado do Conselho de Ministros de 28-10-2025 anuncia submissão de proposta à AR | **Proposta, não regra activa.** A pesquisa dirigida não localizou promulgação/publicação no BR. A confirmação do estado legislativo deve ser repetida antes de qualquer actualização de produção | Registo histórico e comparação futura, sem condicionar o formulário activo | Teste documental de que conteúdo proposto não altera regras publicadas nem snapshots | Não aprovada para execução | -| Regulamento de ordenamento territorial | Lei n.º 19/2007 e Decreto n.º 23/2008; a actividade governamental “Nosso Território” também identifica o Decreto n.º 60/2006 no pacote em revisão | Página oficial da actividade do MTA sobre revisão do pacote; cópias normativas PDUL são referências secundárias governamentais | A página evidencia processo de revisão, não entrada em vigor de projecto. Diplomas, alterações e incidência concreta no DUAT precisam de confronto com BR | Triagem territorial e avisos de jurisdição; nenhuma decisão cartográfica | Tratar intersecção como alerta, nunca como decisão de titularidade/disponibilidade | Pendente | -| Metodologia de regularização por ocupação | Diploma Ministerial n.º 2/2020, referido no Guião de Gestão de Terras Urbanas do PDUL | Guia governamental de gestão urbana (fonte administrativa/técnica, não substitui o diploma); cópia integral do DM e âmbito ainda não revistos aqui | Não activar requisitos ou prazo de ocupação com base no guia apenas | Campos de antecedentes e apoio a reconhecimento/regularização | Requer fonte primária, artigo, jurisdição, vigência e aprovação antes de checklist | Pendente | -| Formulários digitalizados do utilizador | Sete PDFs nas subpastas `Formulario`, `exploracao` e `Consulta` | Inventário local, nomes e hashes no [source-inventory.md](source-inventory.md); classificação vem apenas da pasta | Conteúdo, emissor, data, jurisdição e actualidade por OCR e conferência visual | Referência interna reservada, sem download público nem preenchimento automático | OCR em cópia separada; cada campo conferido contra imagem; ilegível fica pendente | Pendente | -| Guia Nacional mencionado | Não localizado como documento legível e autenticado no acervo disponível | Pasta local indicada foi pesquisada; `DUAT.docx` separado estava bloqueado por outro processo e não foi lido | Não confirmado | Sem checklist jurídica | Não preencher lacunas por inferência | Pendente | +| Matéria | Diploma ou indicação | Fonte consultada e qualidade | Vigência/estado nesta triagem | Área do produto | Teste/controlo | Aprovação | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| Lei de Terras | Lei n.º 19/97, de 1 de Outubro; a reprodução consultada contém, entre outros, arts. 12–15 (aquisição, titulação, registo e prova), art. 19 (plano de exploração) e art. 24 (participação comunitária) | PDF “Lei de terra” na página normativa do PDUL, programa governamental. É reprodução útil para localizar dispositivos, ainda sem confronto página a página com o BR | Lei-base histórica identificada. Consolidação/alterações e vigência artigo a artigo por confirmar no BR e com revisão jurídica | Avisos, categorias de pedido, direitos adquiridos por ocupação e formulação de campos | Testar que ausência de título/registo não é tratada pelo portal como inexistência de direito; nenhuma inferência jurídica a partir de mapa | Pendente de confirmação por jurista fundiário | +| Regulamento rural | Decreto n.º 66/98, de 8 de Dezembro | PDF “Regulamento da Lei de Terra” na página normativa do PDUL; reprodução governamental. Fontes secundárias consultadas indicam alterações pelos Decretos n.º 1/2003, 50/2007 e 43/2010; falta conferir os diplomas e artigos no BR | Texto-base identificado; consolidação e vigência por dispositivo pendentes | Checklist por modalidade, consulta comunitária, plano e processo rural | Não tornar campos ou scans em obrigação até completar a matriz artigo→campo→evidência | Pendente | +| Solo urbano | Decreto n.º 60/2006, de 26 de Dezembro, Regulamento do Solo Urbano | PDF ligado pela página normativa do PDUL; reprodução governamental | Identificado; relação com actos posteriores, regulamentos locais e reformas de ordenamento a validar por jurisdição | Ramificação urbana distinta; não reutilizar automaticamente procedimentos rurais | Caso de teste urbano não deve herdar checklist rural por omissão | Pendente | +| Taxas fundiárias | Diploma Ministerial n.º 47/2020; o sumário do BR n.º 176 (14-09-2020) diz que actualiza taxas do Decreto 66/98 e Decreto 77/99 e revoga o DM n.º 144/2010 | Sumário da Imprensa Nacional de Moçambique (fonte oficial), sem o texto integral/quadros revistos nesta revisão | Existência da actualização confirmada pelo sumário; valor, moeda, âmbito, aplicação temporal e eventual alteração posterior não confirmados | Biblioteca informativa; não calcular nem pedir pagamento no formulário | As taxas não podem ter fallback demonstrativo ou valor de 2010; assegurar ausência de cálculo activo até fonte integral aprovada | Pendente | +| Proposta de nova Lei de Terras | Proposta de Revisão da Lei de Terras, com PDF “Lei de Terras” na página do Parlamento | Parlamento de Moçambique lista a matéria na secção “Projectos e Propostas de Lei”; comunicado do Conselho de Ministros de 28-10-2025 anuncia submissão de proposta à AR | **Proposta, não regra activa.** A pesquisa dirigida não localizou promulgação/publicação no BR. A confirmação do estado legislativo deve ser repetida antes de qualquer actualização de produção | Registo histórico e comparação futura, sem condicionar o formulário activo | Teste documental de que conteúdo proposto não altera regras publicadas nem snapshots | Não aprovada para execução | +| Regulamento de ordenamento territorial | Lei n.º 19/2007 e Decreto n.º 23/2008; a actividade governamental “Nosso Território” também identifica o Decreto n.º 60/2006 no pacote em revisão | Página oficial da actividade do MTA sobre revisão do pacote; cópias normativas PDUL são referências secundárias governamentais | A página evidencia processo de revisão, não entrada em vigor de projecto. Diplomas, alterações e incidência concreta no DUAT precisam de confronto com BR | Triagem territorial e avisos de jurisdição; nenhuma decisão cartográfica | Tratar intersecção como alerta, nunca como decisão de titularidade/disponibilidade | Pendente | +| Metodologia de regularização por ocupação | Diploma Ministerial n.º 2/2020, referido no Guião de Gestão de Terras Urbanas do PDUL | Guia governamental de gestão urbana (fonte administrativa/técnica, não substitui o diploma); cópia integral do DM e âmbito ainda não revistos aqui | Não activar requisitos ou prazo de ocupação com base no guia apenas | Campos de antecedentes e apoio a reconhecimento/regularização | Requer fonte primária, artigo, jurisdição, vigência e aprovação antes de checklist | Pendente | +| Formulários digitalizados do utilizador | Sete PDFs nas subpastas `Formulario`, `exploracao` e `Consulta` | Inventário local, nomes e hashes no [source-inventory.md](source-inventory.md); classificação vem apenas da pasta | Conteúdo, emissor, data, jurisdição e actualidade por OCR e conferência visual | Referência interna reservada, sem download público nem preenchimento automático | OCR em cópia separada; cada campo conferido contra imagem; ilegível fica pendente | Pendente | +| Guia Nacional mencionado | Não localizado como documento legível e autenticado no acervo disponível | Pasta local indicada foi pesquisada; `DUAT.docx` separado estava bloqueado por outro processo e não foi lido | Não confirmado | Sem checklist jurídica | Não preencher lacunas por inferência | Pendente | ## Salvaguardas funcionais que já podem permanecer diff --git a/docs/investmoz/land-assistance/requirement-traceability.md b/docs/investmoz/land-assistance/requirement-traceability.md index 717b2092..57d55832 100644 --- a/docs/investmoz/land-assistance/requirement-traceability.md +++ b/docs/investmoz/land-assistance/requirement-traceability.md @@ -2,25 +2,25 @@ **Revisão:** 2026-10-03. Esta matriz descreve o código presente no repositório; não certifica a validade jurídica de modelos nem a aprovação da funcionalidade para submissão oficial. -| Requisito do prompt | Fonte disponível/estado | Campo ou percurso | Validação/evidência | Teste/estado | -| --- | --- | --- | --- | --- | -| Guia público e limites do InvestMoz | `resources/js/pages/land-assistance/guide.tsx`; não depende de requisito jurídico específico | `/terra-duat` | Aviso de que não atribui DUAT nem confirma disponibilidade ou direitos | `LandAssistanceTest`: guia público 200; coberto | -| Formulário em branco público | Modelo InvestMoz `land-assistance-v1`; scans oficiais não identificados | `/terra-duat/formulario-em-branco` | Impressão/guardar como PDF; explicitamente não oficial e não preenchível | `LandAssistanceTest`: rota pública 200; coberto como modelo imprimível | -| Formulário progressivo, rascunho, retoma e versão | Modelo interno `land-assistance-v1` | `/investidor/terra-duat/novo`, edição e acompanhamento | Autenticação, cifra do payload, versão optimista, histórico cifrado e eventos sem conteúdo pessoal | Testes de feature cobrem criação, propriedade, concorrência e suporte; sem evidência de ensaio browser DUAT nesta revisão | -| Categorias de apoio e actos posteriores | Vocabulário de produto do prompt; modalidades e efeitos legais pendentes | `payload.support_kind`, `request_description` | Valores delimitados no FormRequest; UI não declara elegibilidade ou aprovação | Parcial: classificação jurídica detalhada depende das fontes oficiais | -| Requerente, contacto, representação e minimização | Requisitos do prompt; fundamento de recolha por acto concreto ainda carece de fonte | `applicant_category`, contacto, representação, NUIT | Campos opcionais no rascunho; apoio exige um contacto utilizável e consentimento | Email ou telefone isolado coberto por `LandAssistanceTest`; não é criado endereço sintético | -| Localização, várias parcelas e hierarquia territorial | Catálogo territorial da aplicação; não é cadastro fundiário | `parcels[]`, province/district/admin_post/locality, área e geometria declaradas | UUID, tipos e sequência pai/filho; até 10 parcelas; catálogo/mapa não confirmam direitos | Validação servidor implementada; cenário visual de cascata e retoma do DUAT sem teste browser registado | -| Ocupação, usos, antecedentes e conflito | Declarações do requerente, não inferências do portal | `known_occupants`, `known_improvements`, `prior_right_status`, `conflict_status` | Opções incluem “não sei” e estados qualificados; sem conclusão cadastral | Coberto pelo formulário, sem regra fundiária automática | -| Checklist personalizada por modalidade/jurisdição/fase | **Pendente:** guia, Lei/regulamentos e procedimentos vigentes não foram obtidos em cópia legível/reconciliada | Não há checklist jurídica executável | Não tornar itens dos scans documentos obrigatórios; não inventar fonte, prazo, responsável oficial ou evidência | **Bloqueado por fonte/revisão jurídica** | -| Plano de exploração | Scans `exploracao/scan0003.pdf` e `scan0004.pdf`, classificados só pela pasta; OCR e conferência pendentes | `exploration_plan.activity_rows[]` | Linhas facultativas; período livre, existente/planeado, unidade; não soma áreas anuais | Validação estrutural no FormRequest; OCR não reconciliado | -| Consulta comunitária e pareceres | Scans `Consulta/scan*.pdf`, ainda sem OCR/revisão | `consultation.participants[]`, intervenções, oposição, acordos, documentos referidos | Até 200 participantes; estados comunicados/preparatórios; não gera parecer, assinatura ou acta | Cobertura do formulário; sem validação de execução/interpretação legal | -| Anexos e documentos privados | Armazenamento privado e registo `documents` existente | Documentos no caso | Tipos/tamanho limitados; acesso autenticado/proprietário; quarentena não descarregável | Implementado com bloqueio seguro; **antimalware não configurado**, sem libertação automática | -| Cópia preenchida, histórico e referência | Modelo InvestMoz `land-assistance-v1` | `/imprimir` | Reflecte payload guardado e versão; referência interna; sem assinatura/número oficial simulado | HTML imprimível, não PDF electrónico; correspondência visual/browser ainda sem evidência nesta revisão | -| Apoio e CRM | `InterestSubmission`; migration aditiva `2026_10_03_210000_make_interest_request_email_nullable.php` | `solicitar-apoio` | Email ou telefone; consentimento, transacção, idempotência e recibo interno | Telefone isolado persiste email `NULL`; `LandAssistanceTest` cobre percurso e rollback protegido. Consumidores downstream com email obrigatório ainda carecem de validação operacional | -| Acompanhamento externo e gestão | Nenhuma API pública de DUAT confirmada | Área privada e gestão | Estados internos; factos externos apenas informados ou sustentados por evidência; encaminhamento preparado não é envio | Implementado sem simular integração pública | -| Direitos, disponibilidade e intersecções | Não confirmáveis por dados/catálogos locais | Localização e documentos | Ausência de registo/intersecção não prova ausência de direitos; conflitos não são decididos | Salvaguarda de interface presente; confirmação cabe à entidade competente | -| Segurança, auditoria e reversão | PostgreSQL e padrões locais | Migrations `2026_10_03_200000_create_land_assistance_cases.php` e `2026_10_03_210000_make_interest_request_email_nullable.php` | Payload/revisões cifrados; controlo de propriedade; concorrência optimista; eventos sem payload; upload privado em quarentena; rollback protegido | PostgreSQL 17 QA descartável: `LandAssistanceTest` 6/6 (46 asserções); nenhuma migration aplicada à base operacional | -| Fontes e versões históricas | Inventário read-only em `source-inventory.md` | `land-assistance-v1` | SHA-256 dos sete PDFs; originais fora de `public/`; snapshots preservam versões | Identidade dos ficheiros verificada anteriormente; conteúdo/actualidade pendentes | +| Requisito do prompt | Fonte disponível/estado | Campo ou percurso | Validação/evidência | Teste/estado | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Guia público e limites do InvestMoz | `resources/js/pages/land-assistance/guide.tsx`; não depende de requisito jurídico específico | `/terra-duat` | Aviso de que não atribui DUAT nem confirma disponibilidade ou direitos | `LandAssistanceTest`: guia público 200; coberto | +| Formulário em branco público | Modelo InvestMoz `land-assistance-v1`; scans oficiais não identificados | `/terra-duat/formulario-em-branco` | Impressão/guardar como PDF; explicitamente não oficial e não preenchível | `LandAssistanceTest`: rota pública 200; coberto como modelo imprimível | +| Formulário progressivo, rascunho, retoma e versão | Modelo interno `land-assistance-v1` | `/investidor/terra-duat/novo`, edição e acompanhamento | Autenticação, cifra do payload, versão optimista, histórico cifrado e eventos sem conteúdo pessoal | Testes de feature cobrem criação, propriedade, concorrência e suporte; sem evidência de ensaio browser DUAT nesta revisão | +| Categorias de apoio e actos posteriores | Vocabulário de produto do prompt; modalidades e efeitos legais pendentes | `payload.support_kind`, `request_description` | Valores delimitados no FormRequest; UI não declara elegibilidade ou aprovação | Parcial: classificação jurídica detalhada depende das fontes oficiais | +| Requerente, contacto, representação e minimização | Requisitos do prompt; fundamento de recolha por acto concreto ainda carece de fonte | `applicant_category`, contacto, representação, NUIT | Campos opcionais no rascunho; apoio exige um contacto utilizável e consentimento | Email ou telefone isolado coberto por `LandAssistanceTest`; não é criado endereço sintético | +| Localização, várias parcelas e hierarquia territorial | Catálogo territorial da aplicação; não é cadastro fundiário | `parcels[]`, province/district/admin_post/locality, área e geometria declaradas | UUID, tipos e sequência pai/filho; até 10 parcelas; catálogo/mapa não confirmam direitos | Validação servidor implementada; cenário visual de cascata e retoma do DUAT sem teste browser registado | +| Ocupação, usos, antecedentes e conflito | Declarações do requerente, não inferências do portal | `known_occupants`, `known_improvements`, `prior_right_status`, `conflict_status` | Opções incluem “não sei” e estados qualificados; sem conclusão cadastral | Coberto pelo formulário, sem regra fundiária automática | +| Checklist personalizada por modalidade/jurisdição/fase | **Pendente:** guia, Lei/regulamentos e procedimentos vigentes não foram obtidos em cópia legível/reconciliada | Não há checklist jurídica executável | Não tornar itens dos scans documentos obrigatórios; não inventar fonte, prazo, responsável oficial ou evidência | **Bloqueado por fonte/revisão jurídica** | +| Plano de exploração | Scans `exploracao/scan0003.pdf` e `scan0004.pdf`, classificados só pela pasta; OCR e conferência pendentes | `exploration_plan.activity_rows[]` | Linhas facultativas; período livre, existente/planeado, unidade; não soma áreas anuais | Validação estrutural no FormRequest; OCR não reconciliado | +| Consulta comunitária e pareceres | Scans `Consulta/scan*.pdf`, ainda sem OCR/revisão | `consultation.participants[]`, intervenções, oposição, acordos, documentos referidos | Até 200 participantes; estados comunicados/preparatórios; não gera parecer, assinatura ou acta | Cobertura do formulário; sem validação de execução/interpretação legal | +| Anexos e documentos privados | Armazenamento privado e registo `documents` existente | Documentos no caso | Tipos/tamanho limitados; acesso autenticado/proprietário; quarentena não descarregável | Implementado com bloqueio seguro; **antimalware não configurado**, sem libertação automática | +| Cópia preenchida, histórico e referência | Modelo InvestMoz `land-assistance-v1` | `/imprimir` | Reflecte payload guardado e versão; referência interna; sem assinatura/número oficial simulado | HTML imprimível, não PDF electrónico; correspondência visual/browser ainda sem evidência nesta revisão | +| Apoio e CRM | `InterestSubmission`; migration aditiva `2026_10_03_210000_make_interest_request_email_nullable.php` | `solicitar-apoio` | Email ou telefone; consentimento, transacção, idempotência e recibo interno | Telefone isolado persiste email `NULL`; `LandAssistanceTest` cobre percurso e rollback protegido. Consumidores downstream com email obrigatório ainda carecem de validação operacional | +| Acompanhamento externo e gestão | Nenhuma API pública de DUAT confirmada | Área privada e gestão | Estados internos; factos externos apenas informados ou sustentados por evidência; encaminhamento preparado não é envio | Implementado sem simular integração pública | +| Direitos, disponibilidade e intersecções | Não confirmáveis por dados/catálogos locais | Localização e documentos | Ausência de registo/intersecção não prova ausência de direitos; conflitos não são decididos | Salvaguarda de interface presente; confirmação cabe à entidade competente | +| Segurança, auditoria e reversão | PostgreSQL e padrões locais | Migrations `2026_10_03_200000_create_land_assistance_cases.php` e `2026_10_03_210000_make_interest_request_email_nullable.php` | Payload/revisões cifrados; controlo de propriedade; concorrência optimista; eventos sem payload; upload privado em quarentena; rollback protegido | PostgreSQL 17 QA descartável: `LandAssistanceTest` 6/6 (46 asserções); nenhuma migration aplicada à base operacional | +| Fontes e versões históricas | Inventário read-only em `source-inventory.md` | `land-assistance-v1` | SHA-256 dos sete PDFs; originais fora de `public/`; snapshots preservam versões | Identidade dos ficheiros verificada anteriormente; conteúdo/actualidade pendentes | ## Bloqueios antes de activar checklist legal ou canal oficial @@ -38,9 +38,11 @@ ## Adenda — contacto telefónico sem email (2026-10-03) A regra anterior desta matriz que marcava o email como obrigatório foi substituída no código: o pedido de apoio aceita email ou telefone. Email ausente é persistido como `NULL`, sem endereço inventado. Uma migration aditiva torna a coluna anulável; o rollback recusa-se a remover essa compatibilidade enquanto existirem pedidos só telefónicos. A conversão CRM para lead continua a exigir confirmação de email. Os testes de feature foram escritos, mas não executados porque a base isolada configurada em `127.0.0.1:55441` estava encerrada; não foi tentada ligação à porta operacional 5432. + ### Evidência posterior — migration e percurso telefónico (2026-10-03) A nota anterior de tentativa bloqueada foi superada numa execução posterior: a nova base PostgreSQL 17 foi criada em pasta temporária exclusiva e usada através da porta QA 55441. `LandAssistanceTest` passou 4/4 (42 asserções) e a suite PHP completa passou 251/251 (2.586 asserções); isto executou a migration aditiva em QA. O cluster foi parado. Nenhuma migration foi executada na base da aplicação e a porta 5432 não foi usada. + ### Actualização dos testes de reversibilidade — 2026-10-03 `LandAssistanceTest` agora passa 6/6 (46 asserções), incluindo reversão normal da coluna para não nula quando não existem pedidos telefónicos e bloqueio do rollback quando existem. A suite PHP completa após esses testes passou 253/253 (2.590 asserções) no cluster PostgreSQL QA descartável. O cluster foi parado; nenhuma conexão de teste foi aberta na porta 5432. Nenhuma migration foi aplicada à base local ou de produção. diff --git a/docs/investmoz/land-assistance/source-inventory.md b/docs/investmoz/land-assistance/source-inventory.md index 86d9bf1d..139be6bd 100644 --- a/docs/investmoz/land-assistance/source-inventory.md +++ b/docs/investmoz/land-assistance/source-inventory.md @@ -4,28 +4,28 @@ Pasta observada: `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\Scan\Terras`. A classificação abaixo deriva apenas do nome da subpasta, não da leitura do conteúdo. Os PDFs não apresentam camada de texto detectável nesta verificação; OCR e conferência página a página continuam pendentes. Não foram copiados para `public/` nem usados para criar requisitos automáticos. -| Categoria provisória | Ficheiro original | Bytes | SHA-256 | Tratamento | -| --- | --- | ---: | --- | --- | -| Consulta comunitária/pareceres | `Consulta/scan0001.pdf` | 140871 | `3C1143FD565D92403E0C175ADFBEA293AFC3F187FD6A201BA26E1D45A998D2CA` | Referência reservada; conteúdo por OCR/revisão | -| Consulta comunitária/pareceres | `Consulta/scan0002.pdf` | 88860 | `B6A0CCAC07EEF71294089DDE653F6ED3E600196B75836D69C0E99687E199D6CC` | Referência reservada; conteúdo por OCR/revisão | -| Consulta comunitária/pareceres | `Consulta/scan.pdf` | 174964 | `732FF341BBD4C0C05E8ACCDB6919FA6151D18E50E0C3ED0CD618A4FD69B9018F` | Referência reservada; conteúdo por OCR/revisão | -| Plano de exploração | `exploracao/scan0003.pdf` | 144995 | `F1F5778426D16FAD122F1DF32F40DF1B2CD66F5C56CA41A9EA56499F10ED8D60` | Referência reservada; conteúdo por OCR/revisão | -| Plano de exploração | `exploracao/scan0004.pdf` | 150054 | `54EE525982C78450611A3C4B93F77ECC00493DECDBA4CF982BBC45E52DE1CF01` | Referência reservada; conteúdo por OCR/revisão | -| Formulário/requisitos | `Formulario/scan0006.pdf` | 243123 | `A45CA527E0581B95939F0A35BF107BF78F24E183513E5CE4AD81C0FE6FEC3E53` | Referência reservada; conteúdo por OCR/revisão | -| Formulário/requisitos | `Formulario/scan0005.pdf` | 286614 | `D994A036EFFB269A1BF808164518FC251458379CC15F1A4D5730E8BD746A9A37` | Referência reservada; conteúdo por OCR/revisão | +| Categoria provisória | Ficheiro original | Bytes | SHA-256 | Tratamento | +| ------------------------------ | ------------------------- | -----: | ------------------------------------------------------------------ | ---------------------------------------------- | +| Consulta comunitária/pareceres | `Consulta/scan0001.pdf` | 140871 | `3C1143FD565D92403E0C175ADFBEA293AFC3F187FD6A201BA26E1D45A998D2CA` | Referência reservada; conteúdo por OCR/revisão | +| Consulta comunitária/pareceres | `Consulta/scan0002.pdf` | 88860 | `B6A0CCAC07EEF71294089DDE653F6ED3E600196B75836D69C0E99687E199D6CC` | Referência reservada; conteúdo por OCR/revisão | +| Consulta comunitária/pareceres | `Consulta/scan.pdf` | 174964 | `732FF341BBD4C0C05E8ACCDB6919FA6151D18E50E0C3ED0CD618A4FD69B9018F` | Referência reservada; conteúdo por OCR/revisão | +| Plano de exploração | `exploracao/scan0003.pdf` | 144995 | `F1F5778426D16FAD122F1DF32F40DF1B2CD66F5C56CA41A9EA56499F10ED8D60` | Referência reservada; conteúdo por OCR/revisão | +| Plano de exploração | `exploracao/scan0004.pdf` | 150054 | `54EE525982C78450611A3C4B93F77ECC00493DECDBA4CF982BBC45E52DE1CF01` | Referência reservada; conteúdo por OCR/revisão | +| Formulário/requisitos | `Formulario/scan0006.pdf` | 243123 | `A45CA527E0581B95939F0A35BF107BF78F24E183513E5CE4AD81C0FE6FEC3E53` | Referência reservada; conteúdo por OCR/revisão | +| Formulário/requisitos | `Formulario/scan0005.pdf` | 286614 | `D994A036EFFB269A1BF808164518FC251458379CC15F1A4D5730E8BD746A9A37` | Referência reservada; conteúdo por OCR/revisão | Também foi localizado `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\prompt\por executar\DUAT.docx`. Estava aberto/bloqueado por outro processo e não foi lido nem copiado. O Guia Nacional para Obtenção e Regularização do DUAT continua por localizar como cópia identificada e legível. ### Fontes normativas digitais localizadas em 2026-10-03 -| Material | Fonte e natureza | Uso permitido nesta fase | Estado | -| --- | --- | --- | --- | -| Lei n.º 19/97, de 1 de Outubro | [PDUL — Documentos Normativos](https://www.pdul.gov.mz/Anuncios/Documentos-Normativos), ligação “Lei de terra” para PDF; compilação alojada num programa governamental, reprodução digital do diploma | Identificar estrutura/artigos para revisão; não substituir conferência com o Boletim da República | Texto pesquisável obtido; cópia do BR e reconciliação de alterações ainda pendentes | -| Decreto n.º 66/98, de 8 de Dezembro | Mesma página PDUL, ligação “Regulamento da Lei de Terra” | Referência de trabalho para revisão do regulamento | Texto digital localizado; verificar alterações em conjunto (Decretos n.º 1/2003, 50/2007 e 43/2010 mencionados nas fontes consultadas) | -| Decreto n.º 60/2006, de 26 de Dezembro | Mesma página PDUL, ligação “Regulamento do solo urbano” | Identificar percurso urbano separado do rural | PDF localizado; texto extraído pela pesquisa não foi integralmente confrontado com o BR | -| Diploma Ministerial n.º 47/2020 | [Imprensa Nacional de Moçambique — sumário do BR n.º 176, 14-09-2020](https://www.inm.gov.mz/pt-br/content/sum%C3%A1rio-br-n%C2%BA-176-de-140920-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1439) | Registar que houve actualização de taxas DUAT e revogação expressa do DM n.º 144/2010; não publicar valores sem ler o diploma integral e confirmar aplicação actual | Sumário oficial localizado; anexo/texto integral e vigência actual por verificar | -| Proposta de revisão da Lei de Terras | [Parlamento — Projectos e Propostas de Lei](https://www.parlamento.mz/?page_id=15236), secção “Proposta de Revisão da Lei de Terras”; [texto disponibilizado](https://www.parlamento.mz/wp-content/uploads/2026/03/LEI-DE-TERRAS.pdf) | Comparação e arquivo separados da regra activa | O portal parlamentar classifica o material como proposta. Não tratar o PDF como lei promulgada ou vigente | -| Comunicado do Conselho de Ministros, 28-10-2025 | [Portal do Governo](https://portaldogoverno.gov.mz/wp-content/uploads/2025/10/COMUNICADO-DA-36.a-SOCM2025.pdf) | Confirma apenas que o Governo aprovou uma proposta para submeter à Assembleia da República | Comunicado secundário ao diploma; não demonstra aprovação parlamentar, promulgação ou entrada em vigor | +| Material | Fonte e natureza | Uso permitido nesta fase | Estado | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| Lei n.º 19/97, de 1 de Outubro | [PDUL — Documentos Normativos](https://www.pdul.gov.mz/Anuncios/Documentos-Normativos), ligação “Lei de terra” para PDF; compilação alojada num programa governamental, reprodução digital do diploma | Identificar estrutura/artigos para revisão; não substituir conferência com o Boletim da República | Texto pesquisável obtido; cópia do BR e reconciliação de alterações ainda pendentes | +| Decreto n.º 66/98, de 8 de Dezembro | Mesma página PDUL, ligação “Regulamento da Lei de Terra” | Referência de trabalho para revisão do regulamento | Texto digital localizado; verificar alterações em conjunto (Decretos n.º 1/2003, 50/2007 e 43/2010 mencionados nas fontes consultadas) | +| Decreto n.º 60/2006, de 26 de Dezembro | Mesma página PDUL, ligação “Regulamento do solo urbano” | Identificar percurso urbano separado do rural | PDF localizado; texto extraído pela pesquisa não foi integralmente confrontado com o BR | +| Diploma Ministerial n.º 47/2020 | [Imprensa Nacional de Moçambique — sumário do BR n.º 176, 14-09-2020](https://www.inm.gov.mz/pt-br/content/sum%C3%A1rio-br-n%C2%BA-176-de-140920-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1439) | Registar que houve actualização de taxas DUAT e revogação expressa do DM n.º 144/2010; não publicar valores sem ler o diploma integral e confirmar aplicação actual | Sumário oficial localizado; anexo/texto integral e vigência actual por verificar | +| Proposta de revisão da Lei de Terras | [Parlamento — Projectos e Propostas de Lei](https://www.parlamento.mz/?page_id=15236), secção “Proposta de Revisão da Lei de Terras”; [texto disponibilizado](https://www.parlamento.mz/wp-content/uploads/2026/03/LEI-DE-TERRAS.pdf) | Comparação e arquivo separados da regra activa | O portal parlamentar classifica o material como proposta. Não tratar o PDF como lei promulgada ou vigente | +| Comunicado do Conselho de Ministros, 28-10-2025 | [Portal do Governo](https://portaldogoverno.gov.mz/wp-content/uploads/2025/10/COMUNICADO-DA-36.a-SOCM2025.pdf) | Confirma apenas que o Governo aprovou uma proposta para submeter à Assembleia da República | Comunicado secundário ao diploma; não demonstra aprovação parlamentar, promulgação ou entrada em vigor | O conteúdo de [legal-source-review-2026-10-03.md](legal-source-review-2026-10-03.md) é uma triagem de fontes, não parecer jurídico. A Lei n.º 19/97 obtida no PDUL não deve ser apresentada como consolidação oficial; o acesso ao portal da Imprensa Nacional confirmou sumários, mas não a cópia integral do BR de 1997. A pesquisa dirigida não encontrou publicação/promulgação da proposta até à data desta revisão; isso não substitui uma certidão ou pesquisa exaustiva do BR. diff --git a/docs/investmoz/legal-source-library-migration-2026-10-06.md b/docs/investmoz/legal-source-library-migration-2026-10-06.md index 822f2336..4a0b0ad9 100644 --- a/docs/investmoz/legal-source-library-migration-2026-10-06.md +++ b/docs/investmoz/legal-source-library-migration-2026-10-06.md @@ -12,11 +12,11 @@ Os três ficheiros passaram pelo ClamAV 1.5.4, com assinaturas de 06/10/2026 08: ## Matriz dos documentos importados -| Documento e versão | Módulos e informação sustentada | Estado documental | Revisão/pendências | -|---|---|---|---| -| Lei n.º 22/2022, republicada no 2.º Suplemento do BR 250 | `simulator` como evidência documental; tema IVA. A revisão documental cita o art. 17(1). | Ficheiro descarregado do catálogo oficial AT e comparado byte a byte com o arquivo: 1.834.284 bytes; SHA-256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. Origem oficial confirmada. Situação registada como `amended`; não é apresentado como consolidação integral actual. | Direitos da composição PDF aguardam revisão administrativa; classificação privada e publicação pendente. A taxa ou regra não é activada pelo registo documental. | -| BR n.º 248/2025, Suplemento n.º 1 | `simulator` como evidência; temas ICE, aduaneiro, ISPC, IVA, IRPS e IRPC, sem afirmar uma obrigação por tema. | Ficheiro descarregado do endpoint WebDAV oficial AT e comparado byte a byte com o arquivo: 440.018 bytes; SHA-256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. Origem oficial confirmada. Situação jurídica registada como `unknown`, pois a edição contém vários diplomas cuja vigência deve ser analisada individualmente. | Direitos da composição PDF aguardam revisão administrativa; classificação privada e publicação pendente. Os artigos e a vigência de cada lei não ficam aprovados pela associação temática. | -| BR n.º 89, I Série, 08/06/2017 — aviso de rectificação da Lei n.º 13/2016 | `simulator` como evidência; tema IVA. A ficha INM identifica o aviso na p. 569. | O hash do PDF local é `11F0CAB15EB6E51010952203FFA24FDC533A24FD642E19525FE4F46B17A1F5E5` e coincide com o exemplar arquivado no repositório. A ficha oficial identifica o acto, mas os bytes locais não foram comparados com um download oficial. Origem `identified`; situação jurídica `unknown`. | Cadeia de custódia, autenticidade binária, anexos e efeito temporal por confirmar. Direitos por confirmar; classificação privada e publicação pendente. | +| Documento e versão | Módulos e informação sustentada | Estado documental | Revisão/pendências | +| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Lei n.º 22/2022, republicada no 2.º Suplemento do BR 250 | `simulator` como evidência documental; tema IVA. A revisão documental cita o art. 17(1). | Ficheiro descarregado do catálogo oficial AT e comparado byte a byte com o arquivo: 1.834.284 bytes; SHA-256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. Origem oficial confirmada. Situação registada como `amended`; não é apresentado como consolidação integral actual. | Direitos da composição PDF aguardam revisão administrativa; classificação privada e publicação pendente. A taxa ou regra não é activada pelo registo documental. | +| BR n.º 248/2025, Suplemento n.º 1 | `simulator` como evidência; temas ICE, aduaneiro, ISPC, IVA, IRPS e IRPC, sem afirmar uma obrigação por tema. | Ficheiro descarregado do endpoint WebDAV oficial AT e comparado byte a byte com o arquivo: 440.018 bytes; SHA-256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. Origem oficial confirmada. Situação jurídica registada como `unknown`, pois a edição contém vários diplomas cuja vigência deve ser analisada individualmente. | Direitos da composição PDF aguardam revisão administrativa; classificação privada e publicação pendente. Os artigos e a vigência de cada lei não ficam aprovados pela associação temática. | +| BR n.º 89, I Série, 08/06/2017 — aviso de rectificação da Lei n.º 13/2016 | `simulator` como evidência; tema IVA. A ficha INM identifica o aviso na p. 569. | O hash do PDF local é `11F0CAB15EB6E51010952203FFA24FDC533A24FD642E19525FE4F46B17A1F5E5` e coincide com o exemplar arquivado no repositório. A ficha oficial identifica o acto, mas os bytes locais não foram comparados com um download oficial. Origem `identified`; situação jurídica `unknown`. | Cadeia de custódia, autenticidade binária, anexos e efeito temporal por confirmar. Direitos por confirmar; classificação privada e publicação pendente. | As onze associações foram gravadas como evidência (`applicability=evidence`), nunca como requisito obrigatório. A interface da biblioteca apresenta “Onde este documento é utilizado” e liga a associação `simulator` a `/Simulador`; o próprio simulador já consulta recomendações para esse módulo. Não foram criadas associações a DUAT, licenciamento, constituição de empresas, trabalho, imigração, zonas ou procedimentos porque estes três ficheiros não sustentam esses percursos. @@ -24,14 +24,14 @@ As onze associações foram gravadas como evidência (`applicability=evidence`), As seis linhas de `investment_legal_sources` são referências URL, todas com `validation_status=pending`; não são binários e não foram convertidas em documentos fictícios: -| Referência existente | Situação nesta migração | -|---|---| -| Beluluane Industrial Park — Free Zone; Resolução Interna do Conselho de Ministros n.º 15/99; página APIEX | URL existente; diploma/efeito actual e ficheiro integral por confirmar. | -| Conselho de Ministros — BR n.º 70, 30/08/2013; Decreto n.º 47/2013 | Página INM existente; texto integral e artigos aplicáveis por confirmar. | -| Manga-Mungassa Special Economic Zone; Decreto n.º 22/2012 | Página APIEX existente; diploma e efeito actual por confirmar. | -| BR n.º 46, 06/06/2014; Decreto n.º 28/2014 | Página INM existente; texto integral e artigos aplicáveis por confirmar. | -| Mocuba Industrial Free Zone; Decreto n.º 29/2014 | Página APIEX existente; diploma e efeito actual por confirmar. | -| Nacala Special Economic Zone; Decreto n.º 76/2007 | Página APIEX existente; diploma e efeito actual por confirmar. | +| Referência existente | Situação nesta migração | +| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Beluluane Industrial Park — Free Zone; Resolução Interna do Conselho de Ministros n.º 15/99; página APIEX | URL existente; diploma/efeito actual e ficheiro integral por confirmar. | +| Conselho de Ministros — BR n.º 70, 30/08/2013; Decreto n.º 47/2013 | Página INM existente; texto integral e artigos aplicáveis por confirmar. | +| Manga-Mungassa Special Economic Zone; Decreto n.º 22/2012 | Página APIEX existente; diploma e efeito actual por confirmar. | +| BR n.º 46, 06/06/2014; Decreto n.º 28/2014 | Página INM existente; texto integral e artigos aplicáveis por confirmar. | +| Mocuba Industrial Free Zone; Decreto n.º 29/2014 | Página APIEX existente; diploma e efeito actual por confirmar. | +| Nacala Special Economic Zone; Decreto n.º 76/2007 | Página APIEX existente; diploma e efeito actual por confirmar. | O inventário Terra/DUAT também contém referências URL a Lei n.º 19/97, Decreto n.º 66/98, Decreto n.º 60/2006 e Diploma Ministerial n.º 47/2020. Os ficheiros oficiais correspondentes não foram encontrados no arquivo local indicado e não foram copiados. `docs/investmoz/land-assistance/source-inventory.md` documenta os limites de cada referência. diff --git a/docs/investmoz/partnerships/browser-qa.json b/docs/investmoz/partnerships/browser-qa.json index 7f5d6ed9..65d1d27c 100644 --- a/docs/investmoz/partnerships/browser-qa.json +++ b/docs/investmoz/partnerships/browser-qa.json @@ -1,20 +1,17 @@ { - "target": "anchor_restore_20261007_test:55441", - "passed": true, - "partnershipId": 3, - "roomId": 3, - "draftRecovered": true, - "sourceBacklink": true, - "moderation": true, - "interest": true, - "shortlist": true, - "privateMessage": true, - "bilateralAgreement": true, - "milestoneCompleted": true, - "roomClosed": true, - "viewports": [ - 1440, - 390 - ], - "errors": [] -} \ No newline at end of file + "target": "anchor_restore_20261007_test:55441", + "passed": true, + "partnershipId": 3, + "roomId": 3, + "draftRecovered": true, + "sourceBacklink": true, + "moderation": true, + "interest": true, + "shortlist": true, + "privateMessage": true, + "bilateralAgreement": true, + "milestoneCompleted": true, + "roomClosed": true, + "viewports": [1440, 390], + "errors": [] +} diff --git a/docs/investmoz/partnerships/contracts.md b/docs/investmoz/partnerships/contracts.md index 5ed6c35b..67286c4b 100644 --- a/docs/investmoz/partnerships/contracts.md +++ b/docs/investmoz/partnerships/contracts.md @@ -4,11 +4,11 @@ Não existe entrega externa configurada. Os endpoints públicos são `GET /api/v As escritas usam a sessão existente, CSRF e autorização por recurso. Os contratos das rotas estão em `routes/partnerships.php`. Alterações enviam `version`; uma versão desatualizada resulta em 409. Criação de rascunho e mensagens usam `client_key` UUID: repetir a mesma operação devolve o resultado existente; reutilizar a chave com outro conteúdo resulta em 409. -| Agregado | Estados persistidos | Separação | -| --- | --- | --- | -| Publicação | draft, submitted, returned, rejected, published, suspended, archived; pending legado | Renovar recupera o rascunho e exige nova submissão. Editar uma proposta publicada não publica a alteração automaticamente. | -| Candidatura | submitted, clarification, shortlisted, rejected, withdrawn, negotiation | Negociação de um candidato não encerra outros. A retirada pertence ao candidato; avaliação e decisão ao titular da proposta. | -| Sala/acordo | negotiation, agreement_recorded, agreed, execution, closed, cancelled | Registo de termos não confirma as partes. Só a confirmação de ambas permite acompanhar a execução. Encerramento exige os marcos concluídos. Renovação devolve a negociação sem apagar o histórico dos termos. | +| Agregado | Estados persistidos | Separação | +| ----------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Publicação | draft, submitted, returned, rejected, published, suspended, archived; pending legado | Renovar recupera o rascunho e exige nova submissão. Editar uma proposta publicada não publica a alteração automaticamente. | +| Candidatura | submitted, clarification, shortlisted, rejected, withdrawn, negotiation | Negociação de um candidato não encerra outros. A retirada pertence ao candidato; avaliação e decisão ao titular da proposta. | +| Sala/acordo | negotiation, agreement_recorded, agreed, execution, closed, cancelled | Registo de termos não confirma as partes. Só a confirmação de ambas permite acompanhar a execução. Encerramento exige os marcos concluídos. Renovação devolve a negociação sem apagar o histórico dos termos. | Os eventos persistidos em `partnership_events` e `audit_events` têm envelope v1: `event_id`, `event_type`, `event_version`, `source`, `subject_id`, `occurred_at`, `producer_version`, `correlation_id`, `causation_id`, `data_classification`, `aggregate_id`, `aggregate_version`, `publication_version`, `payload`, `external_delivery`. A sequência do agregado é atribuída com bloqueio transacional; não confunde a versão de publicação com a de cada candidatura ou sala. O autor fica no registo do evento e da auditoria. `external_delivery=not_configured` não afirma transmissão ao CRM. Estes envelopes são internos e não constituem um endpoint público para informação privada. diff --git a/docs/investmoz/partnerships/requirements.md b/docs/investmoz/partnerships/requirements.md index 514b2b9c..5e6414a9 100644 --- a/docs/investmoz/partnerships/requirements.md +++ b/docs/investmoz/partnerships/requirements.md @@ -2,20 +2,20 @@ Especificação: «Bolsas de pareceria Bolsa de Parcerias.docx», versão 1.0, 7 outubro 2026, no acervo de Relatórios. Original, hash e inventário permanecem privados em `storage/app/private/partnership-exchange/`. As instruções do responsável prevalecem sobre as dependências externas e a exigência geral de organização verificada do documento. -| Requisito | Existente | Alteração necessária | Verificação | -| --- | --- | --- | --- | -| Identidade e representação | users, organizations, memberships | Reutilizar sessão; escrita pela conta titular ou representante autorizado; rascunho pessoal permitido | Conta alheia, organizações e papéis | -| Catálogo e propostas antigas | /parcerias, portal_partnerships, moderação | Estender os registos, manter IDs/autores/estados; pesquisa, detalhe, tipos e filtros | Preservação e paginação | -| Dados mestres e origens | opportunities, dossiers, projects, economic_clusters, investment_zones, declarações empresariais | Referências por ID; entradas e ligações nos dois sentidos | Origem pública/privada e identidade | -| Territórios | geo_locations, project_locations, relações de zonas | Várias localizações com fonte e papel; contexto da origem separado de implantação declarada | Hierarquia e ausência de inferência | -| Percurso de publicação | Proposta pending → published | Rascunho, edição, pré-visualização, submissão, devolução/rejeição fundamentada | Concorrência e transições | -| Candidaturas | Pedidos gerais de interesse | Candidaturas próprias da parceria, atualização/retirada, comparação, esclarecimentos e lista curta | Unicidade e isolamento entre candidatos | -| Correspondências | Sem motor da Bolsa | Regras e pesos configuráveis do documento; informação desconhecida separada de incumprimento comprovado | Dados completos/incompletos e explicação | -| Colaboração | investment_messages ligados obrigatoriamente a investment_submissions | Mensagens da sala de parceria, com o mesmo padrão de isolamento e idempotência; documents mestre e scanner existente | Participantes, armazenamento privado e scan | -| Notificações | investment_notifications ligadas a investment_submissions | Caixa local da Bolsa e eventos versionados; sem correio externo obrigatório | Destinatários e leitura | -| Auditoria e CRM | audit_events e CRM interno | Reutilizar auditoria; preparar eventos sem simular sincronização externa | Autor/data/motivo/versão | -| Acordo e acompanhamento | Sem processo próprio | Registo de instrumento/partes/responsáveis/marcos; confirmação das partes distinta de assinatura certificada | Fluxo completo e permissões | -| QA e aplicação | PostgreSQL QA 55441; gestão autenticada | Migração protegida; pré-visualização, backup/restauro e aplicação autenticada | Base ativa intacta e restauro | +| Requisito | Existente | Alteração necessária | Verificação | +| ---------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| Identidade e representação | users, organizations, memberships | Reutilizar sessão; escrita pela conta titular ou representante autorizado; rascunho pessoal permitido | Conta alheia, organizações e papéis | +| Catálogo e propostas antigas | /parcerias, portal_partnerships, moderação | Estender os registos, manter IDs/autores/estados; pesquisa, detalhe, tipos e filtros | Preservação e paginação | +| Dados mestres e origens | opportunities, dossiers, projects, economic_clusters, investment_zones, declarações empresariais | Referências por ID; entradas e ligações nos dois sentidos | Origem pública/privada e identidade | +| Territórios | geo_locations, project_locations, relações de zonas | Várias localizações com fonte e papel; contexto da origem separado de implantação declarada | Hierarquia e ausência de inferência | +| Percurso de publicação | Proposta pending → published | Rascunho, edição, pré-visualização, submissão, devolução/rejeição fundamentada | Concorrência e transições | +| Candidaturas | Pedidos gerais de interesse | Candidaturas próprias da parceria, atualização/retirada, comparação, esclarecimentos e lista curta | Unicidade e isolamento entre candidatos | +| Correspondências | Sem motor da Bolsa | Regras e pesos configuráveis do documento; informação desconhecida separada de incumprimento comprovado | Dados completos/incompletos e explicação | +| Colaboração | investment_messages ligados obrigatoriamente a investment_submissions | Mensagens da sala de parceria, com o mesmo padrão de isolamento e idempotência; documents mestre e scanner existente | Participantes, armazenamento privado e scan | +| Notificações | investment_notifications ligadas a investment_submissions | Caixa local da Bolsa e eventos versionados; sem correio externo obrigatório | Destinatários e leitura | +| Auditoria e CRM | audit_events e CRM interno | Reutilizar auditoria; preparar eventos sem simular sincronização externa | Autor/data/motivo/versão | +| Acordo e acompanhamento | Sem processo próprio | Registo de instrumento/partes/responsáveis/marcos; confirmação das partes distinta de assinatura certificada | Fluxo completo e permissões | +| QA e aplicação | PostgreSQL QA 55441; gestão autenticada | Migração protegida; pré-visualização, backup/restauro e aplicação autenticada | Base ativa intacta e restauro | As tabelas de mensagens/notificações existentes têm chaves obrigatórias para candidaturas de investimento. Não se criam candidaturas fictícias de investimento para contornar essa integridade. A Bolsa tem entidades transacionais próprias, sem novos cadastros de contas, organizações, documentos ou oportunidades de investimento. diff --git a/docs/investmoz/pii-extraction-review-2026-10-03.md b/docs/investmoz/pii-extraction-review-2026-10-03.md index 5e6ffaaf..3e9ae5b7 100644 --- a/docs/investmoz/pii-extraction-review-2026-10-03.md +++ b/docs/investmoz/pii-extraction-review-2026-10-03.md @@ -4,26 +4,26 @@ Data da análise: 2026-10-03. **Este documento é um inventário estrutural e de ## Proveniência -| Campo | Registo | -| --- | --- | -| Ficheiro | `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\portal do investidor\MPD_Relatório Final do PII 26-30_2111.docx` | -| Tamanho / data local | 5.456.742 bytes; 2026-09-08 14:34 (metadata do filesystem) | -| SHA-256 | `BCBF8EC976100CC5E978AEF54050C3E3E9D959B0D361FA80A788528A8E5049A6` | -| Método | Leitura local de `word/document.xml` no DOCX; contagem de parágrafos, tabelas, linhas e cabeçalhos. Sem alteração do documento ou da base de dados. | -| Referência interna | Índices das tabelas pela ordem em `word/document.xml`, contados a partir de 1. O DOCX não foi convertido em páginas PDF; por isso, os índices não são páginas. | +| Campo | Registo | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ficheiro | `C:\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\portal do investidor\MPD_Relatório Final do PII 26-30_2111.docx` | +| Tamanho / data local | 5.456.742 bytes; 2026-09-08 14:34 (metadata do filesystem) | +| SHA-256 | `BCBF8EC976100CC5E978AEF54050C3E3E9D959B0D361FA80A788528A8E5049A6` | +| Método | Leitura local de `word/document.xml` no DOCX; contagem de parágrafos, tabelas, linhas e cabeçalhos. Sem alteração do documento ou da base de dados. | +| Referência interna | Índices das tabelas pela ordem em `word/document.xml`, contados a partir de 1. O DOCX não foi convertido em páginas PDF; por isso, os índices não são páginas. | ## Inventário e qualidade estrutural -| Observação | Resultado verificável | Implicação | -| --- | --- | --- | -| Estrutura integral do corpo | 2.520 parágrafos, 30 tabelas e 353 linhas no total. | O ficheiro contém contexto programático além das tabelas de projectos; não importar o DOCX como se fosse uma única tabela. | -| Tabelas com projectos/custos | Tabelas 11–13, 15–21 e 23–25 (13 tabelas), 174 linhas preenchidas. | Cada linha continua a ser uma proposta/estimativa do plano; não se converte automaticamente em anúncio público. | -| Campos tabulares centrais | Nas 174 linhas, os campos de identificação/nome, localização, custo e horizonte estão preenchidos nas respectivas colunas. | Preenchimento tabular não confirma a exactidão, actualidade nem granularidade geográfica do conteúdo. | -| Título repetido | 174 linhas produzem 173 títulos normalizados; um título aparece mais de uma vez em tabelas diferentes. | Pode representar repetição, fases ou âmbitos distintos. Requer reconciliação humana antes de deduplicar ou criar entidades. | -| Moeda | A tabela 19 tem cabeçalho de custo “USD/MZN”; as demais tabelas seleccionadas indicam USD. | O cabeçalho misto não permite atribuir moeda linha a linha com segurança. Não somar nem converter esse conjunto sem regra e fonte cambial declaradas. | -| Horizonte/período | Presente como coluna nas tabelas de projectos seleccionadas; o documento também contém tabelas de estratégia sem essa coluna. | Preservar literal e referência de tabela; não inferir datas exactas quando o horizonte é textual. | -| Responsáveis, fase/estado e evidência de execução | Não são campos uniformes nas tabelas tabulares seleccionadas. | Não preencher responsáveis, maturidade, disponibilidade, execução, licenciamento ou aprovação por inferência. | -| Localização | Cada linha tem texto na coluna de localização; a análise estrutural não validou topónimos, limites administrativos nem coordenadas. | Manter texto da fonte e estado “não verificado”; mapa apenas depois de validação territorial independente. | +| Observação | Resultado verificável | Implicação | +| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| Estrutura integral do corpo | 2.520 parágrafos, 30 tabelas e 353 linhas no total. | O ficheiro contém contexto programático além das tabelas de projectos; não importar o DOCX como se fosse uma única tabela. | +| Tabelas com projectos/custos | Tabelas 11–13, 15–21 e 23–25 (13 tabelas), 174 linhas preenchidas. | Cada linha continua a ser uma proposta/estimativa do plano; não se converte automaticamente em anúncio público. | +| Campos tabulares centrais | Nas 174 linhas, os campos de identificação/nome, localização, custo e horizonte estão preenchidos nas respectivas colunas. | Preenchimento tabular não confirma a exactidão, actualidade nem granularidade geográfica do conteúdo. | +| Título repetido | 174 linhas produzem 173 títulos normalizados; um título aparece mais de uma vez em tabelas diferentes. | Pode representar repetição, fases ou âmbitos distintos. Requer reconciliação humana antes de deduplicar ou criar entidades. | +| Moeda | A tabela 19 tem cabeçalho de custo “USD/MZN”; as demais tabelas seleccionadas indicam USD. | O cabeçalho misto não permite atribuir moeda linha a linha com segurança. Não somar nem converter esse conjunto sem regra e fonte cambial declaradas. | +| Horizonte/período | Presente como coluna nas tabelas de projectos seleccionadas; o documento também contém tabelas de estratégia sem essa coluna. | Preservar literal e referência de tabela; não inferir datas exactas quando o horizonte é textual. | +| Responsáveis, fase/estado e evidência de execução | Não são campos uniformes nas tabelas tabulares seleccionadas. | Não preencher responsáveis, maturidade, disponibilidade, execução, licenciamento ou aprovação por inferência. | +| Localização | Cada linha tem texto na coluna de localização; a análise estrutural não validou topónimos, limites administrativos nem coordenadas. | Manter texto da fonte e estado “não verificado”; mapa apenas depois de validação territorial independente. | ## Decisão para staging futuro diff --git a/docs/investmoz/pilot/authorized-opportunity-filter-browser-2026-10-04.json b/docs/investmoz/pilot/authorized-opportunity-filter-browser-2026-10-04.json index 9f6362e8..d61462ac 100644 --- a/docs/investmoz/pilot/authorized-opportunity-filter-browser-2026-10-04.json +++ b/docs/investmoz/pilot/authorized-opportunity-filter-browser-2026-10-04.json @@ -1,35 +1,35 @@ { - "base": "http://127.0.0.1:8000", - "testedAt": "2026-10-04T13:58:01.667Z", - "records": [ - { - "key": "all", - "route": "/oportunidades", - "status": 200, - "cards": 6, - "unvalidatedNotice": 6 + "base": "http://127.0.0.1:8000", + "testedAt": "2026-10-04T13:58:01.667Z", + "records": [ + { + "key": "all", + "route": "/oportunidades", + "status": 200, + "cards": 6, + "unvalidatedNotice": 6 + }, + { + "key": "province", + "route": "/oportunidades?province=Nampula", + "status": 200, + "cards": 1, + "unvalidatedNotice": 1 + }, + { + "key": "sector", + "route": "/oportunidades?sector=agronegocio", + "status": 200, + "cards": 1, + "unvalidatedNotice": 1 + } + ], + "errors": [], + "detail": { + "status": 200, + "title": "Unidade de processamento agrícola em Nampula - Laravel", + "unvalidatedNotice": true, + "publishedIntentActionRequiresLogin": true }, - { - "key": "province", - "route": "/oportunidades?province=Nampula", - "status": 200, - "cards": 1, - "unvalidatedNotice": 1 - }, - { - "key": "sector", - "route": "/oportunidades?sector=agronegocio", - "status": 200, - "cards": 1, - "unvalidatedNotice": 1 - } - ], - "errors": [], - "detail": { - "status": 200, - "title": "Unidade de processamento agrícola em Nampula - Laravel", - "unvalidatedNotice": true, - "publishedIntentActionRequiresLogin": true - }, - "pageErrors": [] + "pageErrors": [] } diff --git a/docs/investmoz/pilot/field-usability-pilot-2026-10.md b/docs/investmoz/pilot/field-usability-pilot-2026-10.md index 66422d30..ee360c16 100644 --- a/docs/investmoz/pilot/field-usability-pilot-2026-10.md +++ b/docs/investmoz/pilot/field-usability-pilot-2026-10.md @@ -25,8 +25,8 @@ Escopo: observar o percurso existente e a continuidade do dossier. O código já ## Folha de observação (sem PII) | Sessão | Dispositivo/browser | Tarefa | Resultado (concluída/parou) | Dificuldade ou interpretação | Sugestão literal, sem identificadores | Severidade (bloqueia/atrapalha/menor) | -|---|---|---|---|---|---|---| -| | | | | | | | +| ------ | ------------------- | ------ | --------------------------- | ---------------------------- | ------------------------------------- | ------------------------------------- | +| | | | | | | | Em cada tarefa, observar primeiro sem ajudar. Registar conclusão autónoma, tempo aproximado, pedidos de ajuda, validações compreendidas à primeira, sucesso de recuperação e duplicações percebidas. Perguntar ao participante o que faria a seguir com o resultado e que dados considera declarados, estimados ou verificados. Não guardar respostas do formulário junto das notas de pesquisa. diff --git a/docs/investmoz/pilot/investmoz-catalogue-guide-browser-2026-10-04.json b/docs/investmoz/pilot/investmoz-catalogue-guide-browser-2026-10-04.json index 12873a06..699bc139 100644 --- a/docs/investmoz/pilot/investmoz-catalogue-guide-browser-2026-10-04.json +++ b/docs/investmoz/pilot/investmoz-catalogue-guide-browser-2026-10-04.json @@ -1,86 +1,86 @@ { - "base": "http://127.0.0.1:8000", - "testedAt": "2026-10-04T13:38:08.584Z", - "pages": [ - { - "key": "catalogue", - "status": 200, - "title": "Catálogo de investimento - Laravel", - "bodyTextLength": 7000 - }, - { - "key": "clusters", - "status": 200, - "title": "Corredores, clusters e projectos âncora - Laravel", - "bodyTextLength": 2614 - }, - { - "key": "clusters-again", - "status": 200, - "title": "Corredores, clusters e projectos âncora - Laravel", - "bodyTextLength": 2614 - }, - { - "key": "clusters-after-location-filter", - "status": 200, - "title": "Corredores, clusters e projectos âncora - Laravel", - "bodyTextLength": 2614 - }, - { - "key": "clusters-no-results", - "status": 200, - "title": "Corredores, clusters e projectos âncora - Laravel", - "bodyTextLength": 1177 - }, - { - "key": "anchor-projects", - "status": 200, - "title": "Corredores, clusters e projectos âncora - Laravel", - "bodyTextLength": 6513 - }, - { - "key": "zee-catalogue", - "status": 200, - "title": "ZEE, ZFI e parques industriais - Laravel", - "bodyTextLength": 1104 - }, - { - "key": "existing-business-marketplace", - "status": 200, - "title": "Oportunidades de negócio - Laravel", - "bodyTextLength": 645 - }, - { - "key": "investment-guide-desktop", - "status": 200, - "title": "Como Investir / Procedimentos - Laravel", - "bodyTextLength": 3760 - } - ], - "errors": [], - "catalogueStatus": "6 oportunidades publicadas", - "navigationHasCatalogueGroup": true, - "publishedOpportunityCount": 6, - "clusterRecords": 8, - "legacyclustersRedirect": true, - "legacycorredoresRedirect": true, - "legacyprojectos-ancoraRedirect": true, - "locationFilterApplied": true, - "locationFilteredResults": 0, - "clusterDetailHref": "/carteira/ca6a1427-0e2b-406e-94bd-da23630d5ec4", - "clusterDetailUrl": "http://127.0.0.1:8000/carteira/ca6a1427-0e2b-406e-94bd-da23630d5ec4", - "clusterDetailLoaded": true, - "privateManifestationRequiresAccount": true, - "usefulEmptyState": true, - "anchorDossiers": 15, - "anchorReferenceVisible": true, - "anchorDoesNotClaimOpenTender": true, - "zoneCountText": "0 registos disponíveis", - "businessListingCount": 0, - "guideHasBothPaths": true, - "ideaPathRenders": true, - "activityContextShown": true, - "mobileMenuExpanded": true, - "keyboardFocusVisible": true, - "pageErrors": [] + "base": "http://127.0.0.1:8000", + "testedAt": "2026-10-04T13:38:08.584Z", + "pages": [ + { + "key": "catalogue", + "status": 200, + "title": "Catálogo de investimento - Laravel", + "bodyTextLength": 7000 + }, + { + "key": "clusters", + "status": 200, + "title": "Corredores, clusters e projectos âncora - Laravel", + "bodyTextLength": 2614 + }, + { + "key": "clusters-again", + "status": 200, + "title": "Corredores, clusters e projectos âncora - Laravel", + "bodyTextLength": 2614 + }, + { + "key": "clusters-after-location-filter", + "status": 200, + "title": "Corredores, clusters e projectos âncora - Laravel", + "bodyTextLength": 2614 + }, + { + "key": "clusters-no-results", + "status": 200, + "title": "Corredores, clusters e projectos âncora - Laravel", + "bodyTextLength": 1177 + }, + { + "key": "anchor-projects", + "status": 200, + "title": "Corredores, clusters e projectos âncora - Laravel", + "bodyTextLength": 6513 + }, + { + "key": "zee-catalogue", + "status": 200, + "title": "ZEE, ZFI e parques industriais - Laravel", + "bodyTextLength": 1104 + }, + { + "key": "existing-business-marketplace", + "status": 200, + "title": "Oportunidades de negócio - Laravel", + "bodyTextLength": 645 + }, + { + "key": "investment-guide-desktop", + "status": 200, + "title": "Como Investir / Procedimentos - Laravel", + "bodyTextLength": 3760 + } + ], + "errors": [], + "catalogueStatus": "6 oportunidades publicadas", + "navigationHasCatalogueGroup": true, + "publishedOpportunityCount": 6, + "clusterRecords": 8, + "legacyclustersRedirect": true, + "legacycorredoresRedirect": true, + "legacyprojectos-ancoraRedirect": true, + "locationFilterApplied": true, + "locationFilteredResults": 0, + "clusterDetailHref": "/carteira/ca6a1427-0e2b-406e-94bd-da23630d5ec4", + "clusterDetailUrl": "http://127.0.0.1:8000/carteira/ca6a1427-0e2b-406e-94bd-da23630d5ec4", + "clusterDetailLoaded": true, + "privateManifestationRequiresAccount": true, + "usefulEmptyState": true, + "anchorDossiers": 15, + "anchorReferenceVisible": true, + "anchorDoesNotClaimOpenTender": true, + "zoneCountText": "0 registos disponíveis", + "businessListingCount": 0, + "guideHasBothPaths": true, + "ideaPathRenders": true, + "activityContextShown": true, + "mobileMenuExpanded": true, + "keyboardFocusVisible": true, + "pageErrors": [] } diff --git a/docs/investmoz/pilot/private-dossier-association-browser-2026-10-04.json b/docs/investmoz/pilot/private-dossier-association-browser-2026-10-04.json index 063497b3..0f880bd1 100644 --- a/docs/investmoz/pilot/private-dossier-association-browser-2026-10-04.json +++ b/docs/investmoz/pilot/private-dossier-association-browser-2026-10-04.json @@ -1,11 +1,11 @@ { - "testedAt": "2026-10-04T14:12:49.096Z", - "loginSucceeded": true, - "dossierHttpStatus": 200, - "existingDraftVisible": true, - "cancelledConfirmationKeepsDossierPage": true, - "cancelledConfirmationSentNoCreateRequest": true, - "publicDossierRemainsVisible": true, - "existingDraftContinues": true, - "pageErrors": [] + "testedAt": "2026-10-04T14:12:49.096Z", + "loginSucceeded": true, + "dossierHttpStatus": 200, + "existingDraftVisible": true, + "cancelledConfirmationKeepsDossierPage": true, + "cancelledConfirmationSentNoCreateRequest": true, + "publicDossierRemainsVisible": true, + "existingDraftContinues": true, + "pageErrors": [] } diff --git a/docs/investmoz/pilot/territorial-filter-removal-browser-2026-10-04.json b/docs/investmoz/pilot/territorial-filter-removal-browser-2026-10-04.json index 0812aced..3f687b89 100644 --- a/docs/investmoz/pilot/territorial-filter-removal-browser-2026-10-04.json +++ b/docs/investmoz/pilot/territorial-filter-removal-browser-2026-10-04.json @@ -1,16 +1,16 @@ { - "testedAt": "2026-10-04T15:27:23.846Z", - "path": "/oportunidades", - "initialFilters": { - "province_id": "c20efcce-8af8-588d-84b2-b4b138b6e066", - "sector": "energia" - }, - "initialCount": 1, - "removedFilter": "sector", - "remainingFilters": { - "province_id": "c20efcce-8af8-588d-84b2-b4b138b6e066" - }, - "countAfterRemoval": 2, - "oneFilterRemovedIndividually": true, - "javascriptErrors": [] + "testedAt": "2026-10-04T15:27:23.846Z", + "path": "/oportunidades", + "initialFilters": { + "province_id": "c20efcce-8af8-588d-84b2-b4b138b6e066", + "sector": "energia" + }, + "initialCount": 1, + "removedFilter": "sector", + "remainingFilters": { + "province_id": "c20efcce-8af8-588d-84b2-b4b138b6e066" + }, + "countAfterRemoval": 2, + "oneFilterRemovedIndividually": true, + "javascriptErrors": [] } diff --git a/docs/investmoz/pilot/territorial-search-browser-2026-10-04.json b/docs/investmoz/pilot/territorial-search-browser-2026-10-04.json index 07e4f04e..002638c1 100644 --- a/docs/investmoz/pilot/territorial-search-browser-2026-10-04.json +++ b/docs/investmoz/pilot/territorial-search-browser-2026-10-04.json @@ -1,44 +1,44 @@ { - "testedAt": "2026-10-04T14:29:24.997Z", - "desktop": { - "httpStatus": 200, - "province": { - "id": "059b02ca-7095-577a-8705-4d4c46896747", - "label": "Cabo Delgado (02)" + "testedAt": "2026-10-04T14:29:24.997Z", + "desktop": { + "httpStatus": 200, + "province": { + "id": "059b02ca-7095-577a-8705-4d4c46896747", + "label": "Cabo Delgado (02)" + }, + "url": "http://127.0.0.1:8000/oportunidades?admin_post_id=&before=&cluster_id=&corridor_id=¤cy=&dimension=&district=&district_id=&education=0&geo_location_id=&include_national=0&locality_id=&maturity_level=&max_amount=&min_amount=&park_id=&project_id=&province=&province_id=059b02ca-7095-577a-8705-4d4c46896747&q=§or=&sort=recent&view=grid&zee_id=&zfi_id=", + "listCount": 0, + "mapMatchedCount": 0, + "apiSearchCount": 0, + "csvStatus": 200, + "csvHasBom": true, + "csvDataRows": 0, + "emptyStateVisible": true, + "widenSearchVisible": true, + "activeFilterShown": true }, - "url": "http://127.0.0.1:8000/oportunidades?admin_post_id=&before=&cluster_id=&corridor_id=¤cy=&dimension=&district=&district_id=&education=0&geo_location_id=&include_national=0&locality_id=&maturity_level=&max_amount=&min_amount=&park_id=&project_id=&province=&province_id=059b02ca-7095-577a-8705-4d4c46896747&q=§or=&sort=recent&view=grid&zee_id=&zfi_id=", - "listCount": 0, - "mapMatchedCount": 0, - "apiSearchCount": 0, - "csvStatus": 200, - "csvHasBom": true, - "csvDataRows": 0, - "emptyStateVisible": true, - "widenSearchVisible": true, - "activeFilterShown": true - }, - "mobile": { - "viewport": { - "width": 390, - "height": 844 + "mobile": { + "viewport": { + "width": 390, + "height": 844 + }, + "documentWidth": 390, + "horizontalOverflow": false, + "sameEmptyState": true + }, + "detailReturn": { + "detailHttp": 200, + "backHref": "/oportunidades?province_id=059b02ca-7095-577a-8705-4d4c46896747&include_national=0", + "provincePreservedOnReturn": true, + "returnedListCount": 0 + }, + "businessMarketplace": { + "httpStatus": 200, + "territoryFilterApplied": true, + "viewportWidth": 390, + "documentWidth": 390, + "resultSummary": "0 anúncios de negócios publicados", + "consoleErrors": [] }, - "documentWidth": 390, - "horizontalOverflow": false, - "sameEmptyState": true - }, - "detailReturn": { - "detailHttp": 200, - "backHref": "/oportunidades?province_id=059b02ca-7095-577a-8705-4d4c46896747&include_national=0", - "provincePreservedOnReturn": true, - "returnedListCount": 0 - }, - "businessMarketplace": { - "httpStatus": 200, - "territoryFilterApplied": true, - "viewportWidth": 390, - "documentWidth": 390, - "resultSummary": "0 anúncios de negócios publicados", "consoleErrors": [] - }, - "consoleErrors": [] } diff --git a/docs/investmoz/pilot/territorial-search-followup-2026-10-04.md b/docs/investmoz/pilot/territorial-search-followup-2026-10-04.md index 09f4ec42..47b3bb73 100644 --- a/docs/investmoz/pilot/territorial-search-followup-2026-10-04.md +++ b/docs/investmoz/pilot/territorial-search-followup-2026-10-04.md @@ -47,5 +47,3 @@ O runner configurado em `phpunit.xml` aponta à porta `55441`, onde não havia P - Falta ensaio num dispositivo móvel físico. Não declarar a cobertura territorial transversal concluída. As rotas e pendências por módulo continuam descritas na [auditoria territorial anterior](../territorial-search-audit-2026-10-04.md). - - diff --git a/docs/investmoz/pilot/territorial-search-positive-browser-2026-10-04.json b/docs/investmoz/pilot/territorial-search-positive-browser-2026-10-04.json index 4f088224..58b3fd70 100644 --- a/docs/investmoz/pilot/territorial-search-positive-browser-2026-10-04.json +++ b/docs/investmoz/pilot/territorial-search-positive-browser-2026-10-04.json @@ -1,33 +1,31 @@ { - "testedAt": "2026-10-04T15:22:02.196Z", - "portal": "http://127.0.0.1:8000", - "database": "investemoz", - "path": "/oportunidades", - "province": { - "id": "c20efcce-8af8-588d-84b2-b4b138b6e066", - "name": "Zambezia" - }, - "unfilteredCount": 6, - "filteredCount": 2, - "visibleCountText": "2 oportunidades publicadas", - "visibleTitles": [ - "Processamento de pescado e cadeia fria", - "Mini-rede solar para polo produtivo" - ], - "excludedOtherAreaTitles": [ - "Plataforma de crédito para PME agrícolas", - "Centro logístico provincial", - "Eco-lodge costeiro com integração comunitária", - "Unidade de processamento agrícola em Nampula" - ], - "serverApiMatchesVisibleResults": true, - "mapStatus": [ - "0 investimentos com coordenadas disponíveis." - ], - "mobile": { - "innerWidth": 390, - "scrollWidth": 390, - "noHorizontalOverflow": true - }, - "javascriptErrors": [] + "testedAt": "2026-10-04T15:22:02.196Z", + "portal": "http://127.0.0.1:8000", + "database": "investemoz", + "path": "/oportunidades", + "province": { + "id": "c20efcce-8af8-588d-84b2-b4b138b6e066", + "name": "Zambezia" + }, + "unfilteredCount": 6, + "filteredCount": 2, + "visibleCountText": "2 oportunidades publicadas", + "visibleTitles": [ + "Processamento de pescado e cadeia fria", + "Mini-rede solar para polo produtivo" + ], + "excludedOtherAreaTitles": [ + "Plataforma de crédito para PME agrícolas", + "Centro logístico provincial", + "Eco-lodge costeiro com integração comunitária", + "Unidade de processamento agrícola em Nampula" + ], + "serverApiMatchesVisibleResults": true, + "mapStatus": ["0 investimentos com coordenadas disponíveis."], + "mobile": { + "innerWidth": 390, + "scrollWidth": 390, + "noHorizontalOverflow": true + }, + "javascriptErrors": [] } diff --git a/docs/investmoz/public-portal-sections.md b/docs/investmoz/public-portal-sections.md index c18f929a..17c0836d 100644 --- a/docs/investmoz/public-portal-sections.md +++ b/docs/investmoz/public-portal-sections.md @@ -4,20 +4,20 @@ Implementação local de 27/09/2026, seguindo as sete áreas pedidas pelo propri ## Páginas e percursos -| Área | Caminho e comportamento | -| --- | --- | -| Página inicial | `/`: cabeçalho, banner, sectores activos, Como Investir, oportunidades em destaque, documentos essenciais publicados, apoio e rodapé. | -| Oportunidades | `/oportunidades`: pesquisa, sector, província, estado de preparação/maturidade e dimensão por montante mínimo/máximo e moeda. Valores em moedas diferentes nunca são somados ou comparados automaticamente. Só aparecem oportunidades publicadas. | -| Ficha de oportunidade | `/oportunidades/{slug}`: ficha e manifestação de interesse existentes. | -| Sectores prioritários | `/sectores` e `/sectores/{slug}`: descrição do cadastro, oportunidades publicadas e acesso ao percurso de preparação e apoio. | -| Como Investir | `/como-investir`: seis etapas de preparação, documentação, enquadramento, parceiros e acompanhamento; roteiro de orientação, sem inventar prazos ou requisitos legais obrigatórios. | -| Legislação e Incentivos | `/legislacao`: documentos públicos catalogados como legislação, pesquisáveis por título, categoria e tema, com referência e download. | -| Documentos e Formulários | `/documentos`: catálogo público, pesquisa, categorias, temas e acesso aos formulários digitais. | -| Apoio ao Investidor | `/apoio`: formulário, protocolo, contactos configuráveis e FAQ sobre utilização do portal. | -| Bolsa de Parcerias | `/parcerias`: propostas publicadas, filtros e pedido de contacto intermediado. Conta com email verificado pode propor; gestor autorizado publica ou retira. Contactos e identidade da conta não são expostos na listagem pública. | -| Simulador Fiscal | `/simulador-fiscal`: cálculo aritmético de cenários com base, taxa e redução introduzidas pelo utilizador. Não incorpora taxas legais, elegibilidade ou concessão automática de incentivos. | -| Formulários | `/formularios/investidor` e `/formularios/empresa`: recolha de dados de contacto, sector/assunto, empresa quando aplicável, mensagem e consentimento; gera protocolo. Não equivale a constituição ou registo legal de empresa. | -| Balcão Virtual | `/balcao-virtual`: autenticação e email verificado; mostra pedidos enviados com a conta e estado das suas propostas. Liga ao painel existente da carteira. | +| Área | Caminho e comportamento | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Página inicial | `/`: cabeçalho, banner, sectores activos, Como Investir, oportunidades em destaque, documentos essenciais publicados, apoio e rodapé. | +| Oportunidades | `/oportunidades`: pesquisa, sector, província, estado de preparação/maturidade e dimensão por montante mínimo/máximo e moeda. Valores em moedas diferentes nunca são somados ou comparados automaticamente. Só aparecem oportunidades publicadas. | +| Ficha de oportunidade | `/oportunidades/{slug}`: ficha e manifestação de interesse existentes. | +| Sectores prioritários | `/sectores` e `/sectores/{slug}`: descrição do cadastro, oportunidades publicadas e acesso ao percurso de preparação e apoio. | +| Como Investir | `/como-investir`: seis etapas de preparação, documentação, enquadramento, parceiros e acompanhamento; roteiro de orientação, sem inventar prazos ou requisitos legais obrigatórios. | +| Legislação e Incentivos | `/legislacao`: documentos públicos catalogados como legislação, pesquisáveis por título, categoria e tema, com referência e download. | +| Documentos e Formulários | `/documentos`: catálogo público, pesquisa, categorias, temas e acesso aos formulários digitais. | +| Apoio ao Investidor | `/apoio`: formulário, protocolo, contactos configuráveis e FAQ sobre utilização do portal. | +| Bolsa de Parcerias | `/parcerias`: propostas publicadas, filtros e pedido de contacto intermediado. Conta com email verificado pode propor; gestor autorizado publica ou retira. Contactos e identidade da conta não são expostos na listagem pública. | +| Simulador Fiscal | `/simulador-fiscal`: cálculo aritmético de cenários com base, taxa e redução introduzidas pelo utilizador. Não incorpora taxas legais, elegibilidade ou concessão automática de incentivos. | +| Formulários | `/formularios/investidor` e `/formularios/empresa`: recolha de dados de contacto, sector/assunto, empresa quando aplicável, mensagem e consentimento; gera protocolo. Não equivale a constituição ou registo legal de empresa. | +| Balcão Virtual | `/balcao-virtual`: autenticação e email verificado; mostra pedidos enviados com a conta e estado das suas propostas. Liga ao painel existente da carteira. | O layout partilhado acrescenta breadcrumbs e o botão fixo **Contactar Apoio ao Investidor** às páginas Inertia, incluindo autenticação e gestão. Páginas de erro produzidas directamente pelo servidor não passam por este layout. diff --git a/docs/investmoz/reference-schema-reconciliation.md b/docs/investmoz/reference-schema-reconciliation.md index cd36f4ea..387560a0 100644 --- a/docs/investmoz/reference-schema-reconciliation.md +++ b/docs/investmoz/reference-schema-reconciliation.md @@ -14,24 +14,24 @@ O total inclui schemas legados e tabelas de extensões. Não significa 237 entid As decisões abaixo seleccionam objectos reais. «Estender» não significa que a alteração já foi aplicada. Nenhuma equivalência parcial é declarada cobertura integral. -| Conceito proposto | Objecto existente / evidência no repositório | Cobertura e lacunas | Decisão técnica | -|---|---|---|---| -| Fontes / data_sources | `public.geo_sources` (migration territorial_registry); `public.investment_legal_sources` (investment_zone_foundation); `public.ai_sources`; `investmoz.fontes_dados`; `mne.source_records` | Há cinco mecanismos especializados, com identidades, validações e consumidores diferentes. A fonte jurídica não é automaticamente autorização para IA nem proveniência cartográfica. Não existe correspondência transversal comprovada. | Mapear por compatibilidade, preservando tipo e schema na identidade. Não criar `data_sources` nem fundir registos por URL apenas. A definição do adaptador e da referência transversal ainda falta. | -| Diplomas / legal_instruments | `public.investment_legal_sources`; modelo InvestmentLegalSource; serviço InvestmentZones | Diploma, artigo, entidade competente, URL, vigência, versão, proprietário e revisor já existem. Tipo, número estruturado e publicação não têm colunas próprias. | Estender este mestre com os campos efectivamente necessários; não duplicar diplomas em legal_instruments. Documentos legais do portal conservam a referência ao ficheiro. | -| Organizações / organisations | `public.organizations`, `memberships`; migration investmoz_identity; `mne.organizations` separado | Mestre público UUID; pertenças existentes. MNE não está integrado como autoridade. Tax identifier, contactos e site não devem ser copiados silenciosamente entre módulos. | Reutilizar public.organizations; mapear MNE apenas por vínculo comprovado. Preservar IDs e contas bigint. | -| Áreas administrativas | `public.geo_locations`, `geo_territory_profiles`, `geo_names`; seis tabelas `investmoz.provincia` a `aldeia_quarteirao`; TerritorialRegistry | Mestre aplicacional UUID e cadastro administrativo inteiro coexistem: 1.990 geo_locations e 21.410 unidades nas seis tabelas. Códigos/níveis precisam de correspondência, não de conversão por posição ou nome. | Mapear por compatibilidade; não criar administrative_areas. Preservar a hierarquia completa, IDs e precisão. | -| Entidades territoriais | `economic_clusters`, `development_corridors`, `investment_zones`, `value_chains`, `value_chain_links`; migrations clusters/corridors/zone_foundation | Tipos especializados e relações já existem. Subcluster/nó territorial não têm um mestre identificado; não confundir elo de cadeia com nó geográfico. | Reutilizar mestres especializados e estender relações explícitas; não criar territorial_entities como segundo cadastro. | -| Investimentos | `investment_items`, `investment_item_projects`, `investment_item_opportunities`, `projects`, `opportunities`, `existing_businesses`; InvestmentIdentity | 6 items ligados a 6 oportunidades, 16 projectos e 0 negócios. O item é identidade de ligação, não cópia completa da ficha. IDs UUID e bigint têm contratos distintos. | Reutilizar InvestmentIdentity e os mestres. Não criar investments; não cadastrar projectos em massa só para preencher a ponte. | -| Relações | `cluster_projects`, `corridor_projects`, `corridor_clusters`, `opportunity_*`, `investment_zone_links`, `investment_zone_relations` | FK explícitas; relações de zona já têm fonte, vigência, validação, associação, prioridade e justificação. Nem todos os pivots têm essa cobertura. | Estender somente pivots deficientes; manter os actuais. Não criar investment_relations polimórfica concorrente. | -| Dependências | `complementary_project_links`, migration investmoz_clusters | Direcção anchor→complementary, tipos dependency/enabler/supplier/market, auto-relação recusada. Não foi identificado controlo de ciclos indirectos ou concorrentes. | Estender esta tabela e o serviço de escrita para dependency; não tratar supplier/market como dependência acíclica. Falta implementar/testar controlo de ciclos. | -| Geometrias | `geo_geometries`, `project_locations`, `investment_zone_geometries`; migrations geography/zone_foundation | PostGIS 4326, 2D, não vazio, válido, tipo consistente e limites geográficos já impostos. Zonas distinguem perímetro oficial/influência/site e fonte legal. | Reutilizar. Não criar spatial_features nem duplicar geometria em cada entidade. Pertença jurídica nunca inferida por ST_Intersects. | -| Instalações | `existing_business_objects`; `mne.assets`, `real_estates`, `real_estate_units`, `improvements`; respectivas migrations e serviços | Existem activos/instalações de negócios, mas não um inventário operacional de lotes/capacidade de zonas. Payload privado não é uma tabela de reservas. | Criar apenas a relação/entidade operacional de instalação territorial ausente, após distinguir instalação de activo patrimonial. Não copiar os activos MNE. Ainda não implementada. | -| Regras regulatórias | `investment_legal_sources`, regras de publicação em InvestmentZones, `mne.restrictions`, `portal_resources` | Diplomas, restrições patrimoniais e recursos existem; não foi encontrado cadastro de regras territoriais versionadas dos cinco tipos do anexo. | Criar regras territoriais referenciando o diploma existente, com FK explícitas; preservar restrições patrimoniais. Ainda não implementado. | -| Documentos | `public.documents`, pivots `*_documents`, portal_resources; `mne.documents` e qualificações skills_edu | Mestre público tem UUID, chave, MIME, checksum, classificação e versão; domínios mantêm ligações próprias. Novos campos de vigência/título/fonte exigem mapeamento. | Reutilizar documents e pivots; não criar outro documents nem copiar ficheiros. MNE conserva contrato enquanto não reconciliado. | -| Indicadores | Contagens calculadas em EducationController; `skills_edu.skill_gaps`; conteúdo de investment_dossiers | Existem métricas calculadas, mas não série genérica com definição, unidade, natureza, período, fonte e aprovação. Lacuna de competências não equivale a qualquer indicador. | Criar série de indicadores apenas para essa lacuna, com proprietário por FK explícita. Não persistir cópia das contagens dos dashboards. Ainda não implementado. | -| Manifestações | `interest_requests`, `interest_submission_receipts`, leads, contacts, consents, outbox_events; InterestSubmission e ConvertInterestToLead | Fluxo de interesse e CRM existente; consentimentos e recibos separados. Manifestação não é acto administrativo nem candidatura. | Reutilizar fluxo e IDs; estender contexto/SLA onde faltar, sem expressions_of_interest paralelo. | -| Versões | version_no dos mestres; investment_zone_revisions, geo_territory_revisions, existing_business_revisions, investment_submission_revisions | Snapshots especializados existem; version_no isolado não permite reconstrução. Não há histórico completo de todos os mestres. | Reutilizar históricos por domínio e estender cobertura; não criar entity_versions que duplique snapshots existentes. | -| Auditoria | `public.audit_events`, PortalAudit, trigger imz_audit_events_immutable; auditorias especializadas mne/skills_edu | Auditoria pública já recusa UPDATE/DELETE/TRUNCATE por statement, incluindo zero linhas. Conta proprietária pode alterar DDL. | Reutilizar; preservar tipos e segregação de dados. Não substituir UUID por bigserial nem criar outro audit_events público. | +| Conceito proposto | Objecto existente / evidência no repositório | Cobertura e lacunas | Decisão técnica | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Fontes / data_sources | `public.geo_sources` (migration territorial_registry); `public.investment_legal_sources` (investment_zone_foundation); `public.ai_sources`; `investmoz.fontes_dados`; `mne.source_records` | Há cinco mecanismos especializados, com identidades, validações e consumidores diferentes. A fonte jurídica não é automaticamente autorização para IA nem proveniência cartográfica. Não existe correspondência transversal comprovada. | Mapear por compatibilidade, preservando tipo e schema na identidade. Não criar `data_sources` nem fundir registos por URL apenas. A definição do adaptador e da referência transversal ainda falta. | +| Diplomas / legal_instruments | `public.investment_legal_sources`; modelo InvestmentLegalSource; serviço InvestmentZones | Diploma, artigo, entidade competente, URL, vigência, versão, proprietário e revisor já existem. Tipo, número estruturado e publicação não têm colunas próprias. | Estender este mestre com os campos efectivamente necessários; não duplicar diplomas em legal_instruments. Documentos legais do portal conservam a referência ao ficheiro. | +| Organizações / organisations | `public.organizations`, `memberships`; migration investmoz_identity; `mne.organizations` separado | Mestre público UUID; pertenças existentes. MNE não está integrado como autoridade. Tax identifier, contactos e site não devem ser copiados silenciosamente entre módulos. | Reutilizar public.organizations; mapear MNE apenas por vínculo comprovado. Preservar IDs e contas bigint. | +| Áreas administrativas | `public.geo_locations`, `geo_territory_profiles`, `geo_names`; seis tabelas `investmoz.provincia` a `aldeia_quarteirao`; TerritorialRegistry | Mestre aplicacional UUID e cadastro administrativo inteiro coexistem: 1.990 geo_locations e 21.410 unidades nas seis tabelas. Códigos/níveis precisam de correspondência, não de conversão por posição ou nome. | Mapear por compatibilidade; não criar administrative_areas. Preservar a hierarquia completa, IDs e precisão. | +| Entidades territoriais | `economic_clusters`, `development_corridors`, `investment_zones`, `value_chains`, `value_chain_links`; migrations clusters/corridors/zone_foundation | Tipos especializados e relações já existem. Subcluster/nó territorial não têm um mestre identificado; não confundir elo de cadeia com nó geográfico. | Reutilizar mestres especializados e estender relações explícitas; não criar territorial_entities como segundo cadastro. | +| Investimentos | `investment_items`, `investment_item_projects`, `investment_item_opportunities`, `projects`, `opportunities`, `existing_businesses`; InvestmentIdentity | 6 items ligados a 6 oportunidades, 16 projectos e 0 negócios. O item é identidade de ligação, não cópia completa da ficha. IDs UUID e bigint têm contratos distintos. | Reutilizar InvestmentIdentity e os mestres. Não criar investments; não cadastrar projectos em massa só para preencher a ponte. | +| Relações | `cluster_projects`, `corridor_projects`, `corridor_clusters`, `opportunity_*`, `investment_zone_links`, `investment_zone_relations` | FK explícitas; relações de zona já têm fonte, vigência, validação, associação, prioridade e justificação. Nem todos os pivots têm essa cobertura. | Estender somente pivots deficientes; manter os actuais. Não criar investment_relations polimórfica concorrente. | +| Dependências | `complementary_project_links`, migration investmoz_clusters | Direcção anchor→complementary, tipos dependency/enabler/supplier/market, auto-relação recusada. Não foi identificado controlo de ciclos indirectos ou concorrentes. | Estender esta tabela e o serviço de escrita para dependency; não tratar supplier/market como dependência acíclica. Falta implementar/testar controlo de ciclos. | +| Geometrias | `geo_geometries`, `project_locations`, `investment_zone_geometries`; migrations geography/zone_foundation | PostGIS 4326, 2D, não vazio, válido, tipo consistente e limites geográficos já impostos. Zonas distinguem perímetro oficial/influência/site e fonte legal. | Reutilizar. Não criar spatial_features nem duplicar geometria em cada entidade. Pertença jurídica nunca inferida por ST_Intersects. | +| Instalações | `existing_business_objects`; `mne.assets`, `real_estates`, `real_estate_units`, `improvements`; respectivas migrations e serviços | Existem activos/instalações de negócios, mas não um inventário operacional de lotes/capacidade de zonas. Payload privado não é uma tabela de reservas. | Criar apenas a relação/entidade operacional de instalação territorial ausente, após distinguir instalação de activo patrimonial. Não copiar os activos MNE. Ainda não implementada. | +| Regras regulatórias | `investment_legal_sources`, regras de publicação em InvestmentZones, `mne.restrictions`, `portal_resources` | Diplomas, restrições patrimoniais e recursos existem; não foi encontrado cadastro de regras territoriais versionadas dos cinco tipos do anexo. | Criar regras territoriais referenciando o diploma existente, com FK explícitas; preservar restrições patrimoniais. Ainda não implementado. | +| Documentos | `public.documents`, pivots `*_documents`, portal_resources; `mne.documents` e qualificações skills_edu | Mestre público tem UUID, chave, MIME, checksum, classificação e versão; domínios mantêm ligações próprias. Novos campos de vigência/título/fonte exigem mapeamento. | Reutilizar documents e pivots; não criar outro documents nem copiar ficheiros. MNE conserva contrato enquanto não reconciliado. | +| Indicadores | Contagens calculadas em EducationController; `skills_edu.skill_gaps`; conteúdo de investment_dossiers | Existem métricas calculadas, mas não série genérica com definição, unidade, natureza, período, fonte e aprovação. Lacuna de competências não equivale a qualquer indicador. | Criar série de indicadores apenas para essa lacuna, com proprietário por FK explícita. Não persistir cópia das contagens dos dashboards. Ainda não implementado. | +| Manifestações | `interest_requests`, `interest_submission_receipts`, leads, contacts, consents, outbox_events; InterestSubmission e ConvertInterestToLead | Fluxo de interesse e CRM existente; consentimentos e recibos separados. Manifestação não é acto administrativo nem candidatura. | Reutilizar fluxo e IDs; estender contexto/SLA onde faltar, sem expressions_of_interest paralelo. | +| Versões | version_no dos mestres; investment_zone_revisions, geo_territory_revisions, existing_business_revisions, investment_submission_revisions | Snapshots especializados existem; version_no isolado não permite reconstrução. Não há histórico completo de todos os mestres. | Reutilizar históricos por domínio e estender cobertura; não criar entity_versions que duplique snapshots existentes. | +| Auditoria | `public.audit_events`, PortalAudit, trigger imz_audit_events_immutable; auditorias especializadas mne/skills_edu | Auditoria pública já recusa UPDATE/DELETE/TRUNCATE por statement, incluindo zero linhas. Conta proprietária pode alterar DDL. | Reutilizar; preservar tipos e segregação de dados. Não substituir UUID por bigserial nem criar outro audit_events público. | ## Decisões e limites diff --git a/docs/investmoz/relationships/README.md b/docs/investmoz/relationships/README.md index 6c6c15f1..eb3aae00 100644 --- a/docs/investmoz/relationships/README.md +++ b/docs/investmoz/relationships/README.md @@ -10,38 +10,38 @@ O rótulo original dos dossiers refere programação v4, cujo ficheiro não foi ## Projetos: integração das fichas corrigida; associações confirmadas em QA e na base ativa -| Código | Projeto | Localização documentada e papel | -| --- | --- | --- | -| A01 | MozParks — 20 Parques Industriais | Âmbito nacional; sem implantação individual | -| A02 | Linha Ferroviária Norte–Sul | Âmbito nacional; sem traçado documentado | -| A03 | Desenvolvimento Integrado do Corredor de Nacala | Nampula, Cabo Delgado, Niassa, Tete: secundárias; norte da Zambézia: impacto parcial | -| A04 | Crusse Jamali — ZEE de Turismo | Nampula: principal | -| A05 | Linha de Transmissão Namialo–Metoro | Nampula, Cabo Delgado: secundárias | -| A06 | Cadeia de Valor do Arroz por Projecto de Processamento | Tete, Manica, Zambézia, Sofala: secundárias | -| A07 | Parque Têxtil Integrado | Sofala: principal; Manica, Tete: secundárias | -| A08 | ZEE de Mocuba | Zambézia: principal | -| A09 | Cidadela Parlamentar de Mocuba | Zambézia: principal | -| A10 | Mphanda Nkuwa HPP | Tete: principal | -| A11 | Corredor Ferroviário e Portuário Macuse–Moatize | Tete, Zambézia e distrito de Nicoadala: secundárias | -| A12 | Conectividade e Resiliência Costeira — Compacto II | Zambézia: principal | -| A13 | Vilanculos — Região Integrada de Turismo | Inhambane: principal | -| A14 | ZEE de Agronegócio do Limpopo | Gaza: principal | -| A15 | Regadio na Bacia Hidrográfica do Rio Maputo | Maputo Província: principal; distritos Boane, Moamba, Namaacha: impacto | +| Código | Projeto | Localização documentada e papel | +| ------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------ | +| A01 | MozParks — 20 Parques Industriais | Âmbito nacional; sem implantação individual | +| A02 | Linha Ferroviária Norte–Sul | Âmbito nacional; sem traçado documentado | +| A03 | Desenvolvimento Integrado do Corredor de Nacala | Nampula, Cabo Delgado, Niassa, Tete: secundárias; norte da Zambézia: impacto parcial | +| A04 | Crusse Jamali — ZEE de Turismo | Nampula: principal | +| A05 | Linha de Transmissão Namialo–Metoro | Nampula, Cabo Delgado: secundárias | +| A06 | Cadeia de Valor do Arroz por Projecto de Processamento | Tete, Manica, Zambézia, Sofala: secundárias | +| A07 | Parque Têxtil Integrado | Sofala: principal; Manica, Tete: secundárias | +| A08 | ZEE de Mocuba | Zambézia: principal | +| A09 | Cidadela Parlamentar de Mocuba | Zambézia: principal | +| A10 | Mphanda Nkuwa HPP | Tete: principal | +| A11 | Corredor Ferroviário e Portuário Macuse–Moatize | Tete, Zambézia e distrito de Nicoadala: secundárias | +| A12 | Conectividade e Resiliência Costeira — Compacto II | Zambézia: principal | +| A13 | Vilanculos — Região Integrada de Turismo | Inhambane: principal | +| A14 | ZEE de Agronegócio do Limpopo | Gaza: principal | +| A15 | Regadio na Bacia Hidrográfica do Rio Maputo | Maputo Província: principal; distritos Boane, Moamba, Namaacha: impacto | Para todos faltam coordenadas, posto administrativo e localidade com fonte exata. Os distritos foram associados apenas quando identificados explicitamente; as cidades não foram convertidas em distritos. Não foram inventados pontos. Os estados dos projetos/dossiers foram preservados. ## Clusters: relações existentes apresentadas com navegação nos dois sentidos -| Código | Cluster | Âncoras documentadas | -| --- | --- | --- | -| CLU-01 | Agro-indústria e segurança alimentar | A06, A14, A15 | -| CLU-02 | Energia e industrialização | A01, A05, A08, A10 | -| CLU-03 | Logística, corredores e exportação | A02, A03, A11 | -| CLU-04 | Turismo costeiro e economia azul | A04, A13 | -| CLU-05 | Têxtil, vestuário e algodão | A07 | -| CLU-06 | Mineração e transformação mineral | Sem códigos individuais na fonte | -| CLU-07 | Tecnologia e serviços digitais | Sem códigos individuais na fonte | -| CLU-08 | Financiamento do investimento | Sem códigos individuais na fonte | +| Código | Cluster | Âncoras documentadas | +| ------ | ------------------------------------ | -------------------------------- | +| CLU-01 | Agro-indústria e segurança alimentar | A06, A14, A15 | +| CLU-02 | Energia e industrialização | A01, A05, A08, A10 | +| CLU-03 | Logística, corredores e exportação | A02, A03, A11 | +| CLU-04 | Turismo costeiro e economia azul | A04, A13 | +| CLU-05 | Têxtil, vestuário e algodão | A07 | +| CLU-06 | Mineração e transformação mineral | Sem códigos individuais na fonte | +| CLU-07 | Tecnologia e serviços digitais | Sem códigos individuais na fonte | +| CLU-08 | Financiamento do investimento | Sem códigos individuais na fonte | As 13 relações reutilizam identificadores existentes e permanecem relações documentais de planeamento pendentes, sem confirmação jurídica. Para todos os clusters falta fonte da área própria de abrangência e elos individualizados de cadeias de valor. Os locais dos membros não definem o perímetro do cluster; nenhum ponto único foi atribuído. As atividades/lacunas continuam consultáveis no dossier. Pertença ao cluster não cria automaticamente associações entre todos os projetos e todas as oportunidades. diff --git a/docs/investmoz/relationships/browser-qa.json b/docs/investmoz/relationships/browser-qa.json index dda909f2..f8b9f72b 100644 --- a/docs/investmoz/relationships/browser-qa.json +++ b/docs/investmoz/relationships/browser-qa.json @@ -1,19 +1,16 @@ { - "target": "anchor_restore_20261007_test:55441", - "project": "970d942e-329a-48fa-9426-39828b65be6c", - "cluster": "e563883e-4353-4e07-8969-c774fe2d6d5f", - "passed": true, - "publicNavigation": true, - "administrativeHierarchy": true, - "clusterMemberSites": 2, - "clusterSinglePoint": false, - "territorialMap": true, - "chainLinks": true, - "authenticatedPlanApplied": true, - "replayWithoutDuplicates": true, - "viewports": [ - 1440, - 390 - ], - "errors": [] -} \ No newline at end of file + "target": "anchor_restore_20261007_test:55441", + "project": "970d942e-329a-48fa-9426-39828b65be6c", + "cluster": "e563883e-4353-4e07-8969-c774fe2d6d5f", + "passed": true, + "publicNavigation": true, + "administrativeHierarchy": true, + "clusterMemberSites": 2, + "clusterSinglePoint": false, + "territorialMap": true, + "chainLinks": true, + "authenticatedPlanApplied": true, + "replayWithoutDuplicates": true, + "viewports": [1440, 390], + "errors": [] +} diff --git a/docs/investmoz/relationships/territorial-browser-qa.json b/docs/investmoz/relationships/territorial-browser-qa.json index c85ff370..b4455db4 100644 --- a/docs/investmoz/relationships/territorial-browser-qa.json +++ b/docs/investmoz/relationships/territorial-browser-qa.json @@ -1,14 +1,11 @@ { - "target": "anchor_restore_20261007_test:55441", - "passed": true, - "individualApplications": 2, - "pendingRetained": 1, - "contradictoryRetained": 1, - "noCoordinatesAccepted": true, - "auditAuthorDateSource": true, - "viewports": [ - 1440, - 390 - ], - "errors": [] -} \ No newline at end of file + "target": "anchor_restore_20261007_test:55441", + "passed": true, + "individualApplications": 2, + "pendingRetained": 1, + "contradictoryRetained": 1, + "noCoordinatesAccepted": true, + "auditAuthorDateSource": true, + "viewports": [1440, 390], + "errors": [] +} diff --git a/docs/investmoz/remediation-execution-2026-09-30.md b/docs/investmoz/remediation-execution-2026-09-30.md index 52372998..a370df13 100644 --- a/docs/investmoz/remediation-execution-2026-09-30.md +++ b/docs/investmoz/remediation-execution-2026-09-30.md @@ -1,5 +1,35 @@ # Execução do plano de remediação — 30/09/2026 +## Continuação — 08/10/2026 + +Retomada em `main`, commit `bdd766c`, com árvore inicialmente limpa. Foram +executados os formatadores existentes (`php vendor/bin/pint` e +`node_modules/.bin/vp.cmd fmt`) para tratar os bloqueios técnicos de formatação +registados abaixo. O diff abrange 268 ficheiros antes deste apontamento; +inclui código, contratos, dados JSON e documentação. Não foram executados +importadores, migrations, seeders nem operações de publicação. + +Resultados desta continuação: + +- Pint: aprovado depois da correcção. +- Formatação frontend: os 400 ficheiros verificados passaram. +- TypeScript (`npm.cmd run types:check`): aprovado. +- Testes frontend (`node --test tests/frontend/*.test.mjs`): 144 aprovados, + zero falhas e zero testes ignorados. +- `git diff --check`: aprovado antes deste apontamento. +- `npm.cmd run check`: continua a terminar com código 1; depois de ultrapassar + a formatação, revela 173 avisos de lint e zero erros em 213 ficheiros. + Os avisos não foram suprimidos nem foi alterada a política do verificador. +- PHPStan: iniciado com `--debug --memory-limit=512M`, mas interrompido após + permanecer sem resultado; não declarar aprovação nesta continuação. +- Suite PHP e ciclo PostgreSQL/PostGIS: não executados nesta continuação. + Exigem alvo QA isolado com guarda contra a base activa. + +REM-CI-01 permanece aberto: ainda falta corrigir/rever os avisos de lint, +concluir PHPStan e executar o workflow no commit que contenha as alterações, +com o teste dedicado efectivamente aprovado. Mantém-se o **No-Go global**; +os resultados históricos seguintes conservam as respectivas datas. + ## Resumo Linha de base: branch `main`, commit `3dc7c5c649d433527e2e261e33b8d45eb9e71bd8`, diff --git a/docs/investmoz/roadmap/source-inventory.json b/docs/investmoz/roadmap/source-inventory.json index f562b4a7..236f2901 100644 --- a/docs/investmoz/roadmap/source-inventory.json +++ b/docs/investmoz/roadmap/source-inventory.json @@ -1,218 +1,218 @@ { - "filename": "Roteiro de implementacao de ideias.docx", - "sha256": "a3df44b77878970ef68303615f85a4e88e600ecd6917e6c82d5b29f01b8dfa98", - "paragraph_count": 124, - "table_count": 7, - "media_and_attachments": [ - { - "path": "word/media/image1.png", - "bytes": 1626117, - "sha256": "f44dc437cd1c9b6a978f30fb9ae8977f81e30e4177e05eefb5c5a54cb8626336" - }, - { - "path": "word/media/image2.png", - "bytes": 1745395, - "sha256": "e42c3bb28a1bc0c455904c27f9ba0fc18792efb145fd63c107c3a5166874a4cb" - }, - { - "path": "word/media/image3.png", - "bytes": 1913792, - "sha256": "53fd77a360a7ecb6ff67c733b92d66cdcdaee8ef078d8025243d2cd475a11ca6" - }, - { - "path": "word/media/image4.png", - "bytes": 1394028, - "sha256": "b5f458bff34c013ffffc73730c2637addc7115b0bc58c326881d330969001eca" - }, - { - "path": "word/media/image5.png", - "bytes": 1382473, - "sha256": "f0dbbc1025013174f5bedc453d1e8d476456e36f307850ff1bd66765fdeda09a" - }, - { - "path": "word/media/image6.png", - "bytes": 1313377, - "sha256": "ab52dae6a50fb216b8daaea09b72df019ce9e93ec5b0fb1a3ba8f7eb670e4b02" - }, - { - "path": "word/media/image7.png", - "bytes": 1437364, - "sha256": "72afd50f29f2d2c6e322927a56ba04843c1ca1fa7b9797c5dd91047eb6777e9f" - }, - { - "path": "word/media/image8.png", - "bytes": 1122339, - "sha256": "fc8529eb2af0d89f83003143e0afd039a7e8948ea00aa615762bc669c9d31097" - } - ], - "source_verification": "Documento fornecido pelo proprietário; afirmações jurídicas não verificadas nesta integração", - "publication": "Conteúdo editorial integrado em /como-investir; original preservado na localização de proveniência, sem endpoint de download público", - "fiscal_activation": false, - "local_source_status": "Hash do DOCX, oito imagens, 124 parágrafos e sete tabelas verificados localmente em 2026-10-07. Células verticalmente unidas repetem o título na transcrição.", - "original_source_path": "C:\\Mac\\Home\\Documents\\TEMPO\\Portal Tempo\\Servicos Tempo\\Relatorios\\Roteiro de implementacao de ideias.docx", - "archive_entry_count": 23, - "archive_inventories": [ - { - "kind": "code_package", - "sha256": "4671bb9584c0f56467f9d446150584368f7c250ba35d02669d22253a11a7d0ec", - "entries": [ - { - "path": "resources/js/pages/portal/services.tsx", - "bytes": 59843, - "sha256": "2e527a07d25823b65dea75607aacd07f369067d3b2d3a9dc558439ef4f970b39" - }, - { - "path": "resources/js/components/investment-roadmap.tsx", - "bytes": 11296, - "sha256": "03dcefe6b817235c43d0b07c5a01d127b6b129ec36c0e853078f7fc267eff4cc" - }, - { - "path": "resources/js/lib/investment-roadmap.ts", - "bytes": 6905, - "sha256": "058eabaca781339a106c64c47cafc2a02bddf45029dac7072741ea3719dd649f" - }, - { - "path": "resources/js/lib/investment-roadmap-source.ts", - "bytes": 62315, - "sha256": "cf92f00d85f946596bbca78ab0abec906a8683088e8aa6eba078ee480477648e" - }, - { - "path": "docs/investmoz/roadmap/source-inventory.json", - "bytes": 1789, - "sha256": "269a962c204fa37aa6a88ccf7a4beebbcd8e0155c298ec36023c34e327cfcadf" - }, - { - "path": "docs/investmoz/roadmap/README.md", - "bytes": 4178, - "sha256": "3977f06a945d9259f33bdabe248a7320ec8ef766c931816e00392a8efb4fab31" - }, - { - "path": "APLICAR-NO-CODEX.txt", - "bytes": 705, - "sha256": "ea008ee99e653214196004b6befa8c76fd7bcf52bfbffa560f88e0189d29ca9a" - } - ] - }, - { - "kind": "original_docx", - "sha256": "a3df44b77878970ef68303615f85a4e88e600ecd6917e6c82d5b29f01b8dfa98", - "entries": [ - { - "path": "[Content_Types].xml", - "bytes": 1759, - "sha256": "4a1365af812389914fca6ba2b43b3ddd286bf247c8fd4ec0c18cecf7a9d40cc2" - }, - { - "path": "_rels/.rels", - "bytes": 590, - "sha256": "e19238d7a71fa7a2490776252686f70e2de6238c87cd509b5e3a3cc07c2ea4df" - }, - { - "path": "word/document.xml", - "bytes": 514329, - "sha256": "065366836a2130b4b661a28422af761d001a6655e4f50245d4f9f804881fcf01" - }, - { - "path": "word/_rels/document.xml.rels", - "bytes": 3832, - "sha256": "11c9e3d61a3b37703b2c79a387907b8f0054d0946ccb884bda99ae8782db3c37" - }, - { - "path": "word/footnotes.xml", - "bytes": 3876, - "sha256": "e444b5bb0f294227b2a624d38f7da5c8ae86b499f079b1479bf1aeabf1872ce9" - }, - { - "path": "word/endnotes.xml", - "bytes": 3190, - "sha256": "30493199bc787f6b465a027b89a8604c42adb5e56d78bda74c5cae9bc029ca07" - }, - { - "path": "word/_rels/footnotes.xml.rels", - "bytes": 330, - "sha256": "c95592af69460ddf388d566972d8210a89b1b5f53b619b14efbcde552e94a1f6" - }, - { - "path": "word/media/image1.png", - "bytes": 1626117, - "sha256": "f44dc437cd1c9b6a978f30fb9ae8977f81e30e4177e05eefb5c5a54cb8626336" - }, - { - "path": "word/media/image2.png", - "bytes": 1745395, - "sha256": "e42c3bb28a1bc0c455904c27f9ba0fc18792efb145fd63c107c3a5166874a4cb" - }, - { - "path": "word/media/image3.png", - "bytes": 1913792, - "sha256": "53fd77a360a7ecb6ff67c733b92d66cdcdaee8ef078d8025243d2cd475a11ca6" - }, - { - "path": "word/media/image4.png", - "bytes": 1394028, - "sha256": "b5f458bff34c013ffffc73730c2637addc7115b0bc58c326881d330969001eca" - }, - { - "path": "word/media/image5.png", - "bytes": 1382473, - "sha256": "f0dbbc1025013174f5bedc453d1e8d476456e36f307850ff1bd66765fdeda09a" - }, - { - "path": "word/media/image6.png", - "bytes": 1313377, - "sha256": "ab52dae6a50fb216b8daaea09b72df019ce9e93ec5b0fb1a3ba8f7eb670e4b02" - }, - { - "path": "word/media/image7.png", - "bytes": 1437364, - "sha256": "72afd50f29f2d2c6e322927a56ba04843c1ca1fa7b9797c5dd91047eb6777e9f" - }, - { - "path": "word/media/image8.png", - "bytes": 1122339, - "sha256": "fc8529eb2af0d89f83003143e0afd039a7e8948ea00aa615762bc669c9d31097" - }, - { - "path": "word/theme/theme1.xml", - "bytes": 8719, - "sha256": "934c6296997f1914b39bfd2e4626b1ebd661948d525ff925424c2f9b3af70a7d" - }, - { - "path": "word/settings.xml", - "bytes": 7239, - "sha256": "1fc0054b25d049a9244a25af09c477b70cdf2c11e3b4bae51bbad4f4e0758054" - }, - { - "path": "word/numbering.xml", - "bytes": 49017, - "sha256": "1a6fbd1d8d187a64a49ab5ba9cfe05e592261d76c1fd448ec395bee3fd2c95b5" - }, - { - "path": "word/styles.xml", - "bytes": 47473, - "sha256": "f50b28a2fe1fea1318d62f48e6a061a5e1439f95050bf543b0999c070b83199a" - }, - { - "path": "word/webSettings.xml", - "bytes": 5242, - "sha256": "d80227d86b36c05189af856813ebec6af695de1d30eefdf8ce28cb94e82d6ef6" - }, - { - "path": "word/fontTable.xml", - "bytes": 2813, - "sha256": "ddfafc333ff74a6df8b04b510246ac8597987efbdb50513b6c481c32816d9845" - }, - { - "path": "docProps/core.xml", - "bytes": 748, - "sha256": "da54af1d6b9e6edf46cea7a42ae6b5dd74b0ecb7d9a394ba0bdc9a2532ea6dc1" - }, - { - "path": "docProps/app.xml", - "bytes": 996, - "sha256": "f1f1a8894ff9380d119505d4c7b43f86837e5f276ef4018ebd59ce69904320ca" - } - ] - } - ] + "filename": "Roteiro de implementacao de ideias.docx", + "sha256": "a3df44b77878970ef68303615f85a4e88e600ecd6917e6c82d5b29f01b8dfa98", + "paragraph_count": 124, + "table_count": 7, + "media_and_attachments": [ + { + "path": "word/media/image1.png", + "bytes": 1626117, + "sha256": "f44dc437cd1c9b6a978f30fb9ae8977f81e30e4177e05eefb5c5a54cb8626336" + }, + { + "path": "word/media/image2.png", + "bytes": 1745395, + "sha256": "e42c3bb28a1bc0c455904c27f9ba0fc18792efb145fd63c107c3a5166874a4cb" + }, + { + "path": "word/media/image3.png", + "bytes": 1913792, + "sha256": "53fd77a360a7ecb6ff67c733b92d66cdcdaee8ef078d8025243d2cd475a11ca6" + }, + { + "path": "word/media/image4.png", + "bytes": 1394028, + "sha256": "b5f458bff34c013ffffc73730c2637addc7115b0bc58c326881d330969001eca" + }, + { + "path": "word/media/image5.png", + "bytes": 1382473, + "sha256": "f0dbbc1025013174f5bedc453d1e8d476456e36f307850ff1bd66765fdeda09a" + }, + { + "path": "word/media/image6.png", + "bytes": 1313377, + "sha256": "ab52dae6a50fb216b8daaea09b72df019ce9e93ec5b0fb1a3ba8f7eb670e4b02" + }, + { + "path": "word/media/image7.png", + "bytes": 1437364, + "sha256": "72afd50f29f2d2c6e322927a56ba04843c1ca1fa7b9797c5dd91047eb6777e9f" + }, + { + "path": "word/media/image8.png", + "bytes": 1122339, + "sha256": "fc8529eb2af0d89f83003143e0afd039a7e8948ea00aa615762bc669c9d31097" + } + ], + "source_verification": "Documento fornecido pelo proprietário; afirmações jurídicas não verificadas nesta integração", + "publication": "Conteúdo editorial integrado em /como-investir; original preservado na localização de proveniência, sem endpoint de download público", + "fiscal_activation": false, + "local_source_status": "Hash do DOCX, oito imagens, 124 parágrafos e sete tabelas verificados localmente em 2026-10-07. Células verticalmente unidas repetem o título na transcrição.", + "original_source_path": "C:\\Mac\\Home\\Documents\\TEMPO\\Portal Tempo\\Servicos Tempo\\Relatorios\\Roteiro de implementacao de ideias.docx", + "archive_entry_count": 23, + "archive_inventories": [ + { + "kind": "code_package", + "sha256": "4671bb9584c0f56467f9d446150584368f7c250ba35d02669d22253a11a7d0ec", + "entries": [ + { + "path": "resources/js/pages/portal/services.tsx", + "bytes": 59843, + "sha256": "2e527a07d25823b65dea75607aacd07f369067d3b2d3a9dc558439ef4f970b39" + }, + { + "path": "resources/js/components/investment-roadmap.tsx", + "bytes": 11296, + "sha256": "03dcefe6b817235c43d0b07c5a01d127b6b129ec36c0e853078f7fc267eff4cc" + }, + { + "path": "resources/js/lib/investment-roadmap.ts", + "bytes": 6905, + "sha256": "058eabaca781339a106c64c47cafc2a02bddf45029dac7072741ea3719dd649f" + }, + { + "path": "resources/js/lib/investment-roadmap-source.ts", + "bytes": 62315, + "sha256": "cf92f00d85f946596bbca78ab0abec906a8683088e8aa6eba078ee480477648e" + }, + { + "path": "docs/investmoz/roadmap/source-inventory.json", + "bytes": 1789, + "sha256": "269a962c204fa37aa6a88ccf7a4beebbcd8e0155c298ec36023c34e327cfcadf" + }, + { + "path": "docs/investmoz/roadmap/README.md", + "bytes": 4178, + "sha256": "3977f06a945d9259f33bdabe248a7320ec8ef766c931816e00392a8efb4fab31" + }, + { + "path": "APLICAR-NO-CODEX.txt", + "bytes": 705, + "sha256": "ea008ee99e653214196004b6befa8c76fd7bcf52bfbffa560f88e0189d29ca9a" + } + ] + }, + { + "kind": "original_docx", + "sha256": "a3df44b77878970ef68303615f85a4e88e600ecd6917e6c82d5b29f01b8dfa98", + "entries": [ + { + "path": "[Content_Types].xml", + "bytes": 1759, + "sha256": "4a1365af812389914fca6ba2b43b3ddd286bf247c8fd4ec0c18cecf7a9d40cc2" + }, + { + "path": "_rels/.rels", + "bytes": 590, + "sha256": "e19238d7a71fa7a2490776252686f70e2de6238c87cd509b5e3a3cc07c2ea4df" + }, + { + "path": "word/document.xml", + "bytes": 514329, + "sha256": "065366836a2130b4b661a28422af761d001a6655e4f50245d4f9f804881fcf01" + }, + { + "path": "word/_rels/document.xml.rels", + "bytes": 3832, + "sha256": "11c9e3d61a3b37703b2c79a387907b8f0054d0946ccb884bda99ae8782db3c37" + }, + { + "path": "word/footnotes.xml", + "bytes": 3876, + "sha256": "e444b5bb0f294227b2a624d38f7da5c8ae86b499f079b1479bf1aeabf1872ce9" + }, + { + "path": "word/endnotes.xml", + "bytes": 3190, + "sha256": "30493199bc787f6b465a027b89a8604c42adb5e56d78bda74c5cae9bc029ca07" + }, + { + "path": "word/_rels/footnotes.xml.rels", + "bytes": 330, + "sha256": "c95592af69460ddf388d566972d8210a89b1b5f53b619b14efbcde552e94a1f6" + }, + { + "path": "word/media/image1.png", + "bytes": 1626117, + "sha256": "f44dc437cd1c9b6a978f30fb9ae8977f81e30e4177e05eefb5c5a54cb8626336" + }, + { + "path": "word/media/image2.png", + "bytes": 1745395, + "sha256": "e42c3bb28a1bc0c455904c27f9ba0fc18792efb145fd63c107c3a5166874a4cb" + }, + { + "path": "word/media/image3.png", + "bytes": 1913792, + "sha256": "53fd77a360a7ecb6ff67c733b92d66cdcdaee8ef078d8025243d2cd475a11ca6" + }, + { + "path": "word/media/image4.png", + "bytes": 1394028, + "sha256": "b5f458bff34c013ffffc73730c2637addc7115b0bc58c326881d330969001eca" + }, + { + "path": "word/media/image5.png", + "bytes": 1382473, + "sha256": "f0dbbc1025013174f5bedc453d1e8d476456e36f307850ff1bd66765fdeda09a" + }, + { + "path": "word/media/image6.png", + "bytes": 1313377, + "sha256": "ab52dae6a50fb216b8daaea09b72df019ce9e93ec5b0fb1a3ba8f7eb670e4b02" + }, + { + "path": "word/media/image7.png", + "bytes": 1437364, + "sha256": "72afd50f29f2d2c6e322927a56ba04843c1ca1fa7b9797c5dd91047eb6777e9f" + }, + { + "path": "word/media/image8.png", + "bytes": 1122339, + "sha256": "fc8529eb2af0d89f83003143e0afd039a7e8948ea00aa615762bc669c9d31097" + }, + { + "path": "word/theme/theme1.xml", + "bytes": 8719, + "sha256": "934c6296997f1914b39bfd2e4626b1ebd661948d525ff925424c2f9b3af70a7d" + }, + { + "path": "word/settings.xml", + "bytes": 7239, + "sha256": "1fc0054b25d049a9244a25af09c477b70cdf2c11e3b4bae51bbad4f4e0758054" + }, + { + "path": "word/numbering.xml", + "bytes": 49017, + "sha256": "1a6fbd1d8d187a64a49ab5ba9cfe05e592261d76c1fd448ec395bee3fd2c95b5" + }, + { + "path": "word/styles.xml", + "bytes": 47473, + "sha256": "f50b28a2fe1fea1318d62f48e6a061a5e1439f95050bf543b0999c070b83199a" + }, + { + "path": "word/webSettings.xml", + "bytes": 5242, + "sha256": "d80227d86b36c05189af856813ebec6af695de1d30eefdf8ce28cb94e82d6ef6" + }, + { + "path": "word/fontTable.xml", + "bytes": 2813, + "sha256": "ddfafc333ff74a6df8b04b510246ac8597987efbdb50513b6c481c32816d9845" + }, + { + "path": "docProps/core.xml", + "bytes": 748, + "sha256": "da54af1d6b9e6edf46cea7a42ae6b5dd74b0ecb7d9a394ba0bdc9a2532ea6dc1" + }, + { + "path": "docProps/app.xml", + "bytes": 996, + "sha256": "f1f1a8894ff9380d119505d4c7b43f86837e5f276ef4018ebd59ce69904320ca" + } + ] + } + ] } diff --git a/docs/investmoz/simulations/current-status-2026-10-03.md b/docs/investmoz/simulations/current-status-2026-10-03.md index 33f9cbbc..98bc0645 100644 --- a/docs/investmoz/simulations/current-status-2026-10-03.md +++ b/docs/investmoz/simulations/current-status-2026-10-03.md @@ -2,40 +2,41 @@ Este quadro reconcilia o resumo histórico de `simulator-evolution-progress.md` com as adendas posteriores. É o estado técnico proposto para revisão; não altera gates humanos nem declara aprovação fiscal ou contabilística. -| Etapa | Estado actual | Evidência disponível | Pendências que impedem conclusão | -| --- | --- | --- | --- | -| 0 — diagnóstico | Concluída tecnicamente | Arquitectura, rotas, stack, CI, API e suites inventariados no ledger. | Revisão e aceitação de produto. | -| 1 — contratos | Concluída tecnicamente | Guards runtime e testes cobrem criação/polling/leitura, histórico, drafts/conversão e contratos de questionário, relatório, jobs e erros; OpenAPI tipado; testes PHP confirmam respostas API e round-trip entre snapshot/relatório persistidos e resposta. | Endpoints de questionário, jobs e relatório não são consumidos pela interface actual; se futuramente forem integrados, ligar então os guards existentes. | -| 2 — formulário público | Parcial | Percursos por perfil, página/menu públicos, validação, modo avançado, privacidade anónima e navegação por teclado; Edge CDP verificou 390/768/1024/1440 px, foco em 12 destinos nomeados/visíveis, zero controlos sem nome/overflow e GET `/api/v1/simulation-drafts/current` 200 no PostgreSQL/PostGIS QA. Evidência: [relatório browser](simulator-browser-audit-2026-10-03.json), [script reutilizável](../../../tools/simulator-a11y-browser.mjs). | Testes com leitor de ecrã e dispositivo físico, validação manual do percurso e revisão de produto. O smoke test não calcula nem testa submissões de escrita. | -| 3 — calculadoras | Parcial, educativas | Orçamento, break-even, financiamento e amortização; testes exactos cobrem juros zero, taxas nominais/efectivas, carência, comissões, reconciliação em cêntimos e última prestação. | Modelo não é oferta de credor; revisão financeira institucional e cobertura de necessidades pessoais mais ampla. | -| 4 — fontes/governação | Parcial; gate humano pendente | Texto do BR 248 e republicação da Lei 22/2022 arquivados/revistos; ciclo de governação de regras implementado. A taxa geral de IVA de 16% está confirmada no art. 17(1) da republicação oficial. | Regulamentos anunciados de 2026, regras por âmbito/vigência, INSS, pauta integral, revisão e aprovação formal do Fiscalista. INM encaminha BR 186/187 para a Loja; as fichas abertas não expõem o conteúdo. | -| 5 — motor fiscal | Infraestrutura interna parcial; cálculo público bloqueado | DSL whitelist/DAG, aritmética racional, arredondamento explícito, resolvedor de parâmetros por período/contexto e governação versionada; testes focados 9/9 + 4/4. | Integração com RunSimulation e snapshot explicável, fontes oficiais autenticadas, arredondamento aprovado por componente e casos golden revistos por Fiscalista. | -| 6 — plano financeiro | Parcial, implementado em grande parte | Plano empresarial v1–v17; calendário de pagamentos de passivos de abertura, com saldo final exposto; versões anteriores legíveis. Suite financeira/frontend verde. | Validação contabilística institucional e incumprimento/fornecedores partilhados não modelados. | -| 7 — ponte financeira/fiscal | Parcial | Ponte financeira por mês; tesouraria aceita imposto fixo ou calendário variável por pressuposto do utilizador, mantém meses ausentes indeterminados e separa créditos/pedidos/reconhecimentos IVA do caixa. Suite de calendário 11/11. | Calendário local não persistido; classificação de dedutibilidade e regras fiscais aprovadas continuam pendentes. | -| 8 — resultados/exportação | Concluída tecnicamente para CSV; validação global parcial | Ecrã/CSV reconciliam snapshots e hashes; calculadoras, tesouraria, plano e resultado aritmético têm CSV. PDF não é requisito desta etapa no anexo. | Revisão assistiva manual e gates institucionais continuam pendentes. | -| 9 — conta/Project/CRM | Parcial | Histórico paginado autenticado, isolamento, consentimento, optimistic locking e conversão escolhida para rascunho cifrado, com versão/hash e deduplicação. | Não submete/cria projecto no CRM; teste de integração e revisão assistiva do fluxo real pendentes. | -| 10 — incentivos/extensões | Parcial informativa/territorial; benefícios e cálculos regulados desactivados | Zonas públicas exigem fonte vigente, registos validados, relações/geo revistas; interface esclarece que zona não atribui benefício/licença/autorização. InvestmentZoneTest 6/6, 104 asserções. | Inventário validado por domínio; fontes oficiais/autenticidade; regras fiscais/aduaneiras/cambiais e aprovação competente; revisão de produto/browser. | -| 11 — verificação/entrega | Parcial | Regressão PHP integral 253/253 (2.590 asserções), frontend 119/119, TypeScript, PHPStan 0 erros, build Vite 2.397 módulos; testes de zonas 6/6. | Revisão manual com leitor de ecrã/dispositivo, aprovação de produto e gates fiscais/jurídicos. | +| Etapa | Estado actual | Evidência disponível | Pendências que impedem conclusão | +| --------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 — diagnóstico | Concluída tecnicamente | Arquitectura, rotas, stack, CI, API e suites inventariados no ledger. | Revisão e aceitação de produto. | +| 1 — contratos | Concluída tecnicamente | Guards runtime e testes cobrem criação/polling/leitura, histórico, drafts/conversão e contratos de questionário, relatório, jobs e erros; OpenAPI tipado; testes PHP confirmam respostas API e round-trip entre snapshot/relatório persistidos e resposta. | Endpoints de questionário, jobs e relatório não são consumidos pela interface actual; se futuramente forem integrados, ligar então os guards existentes. | +| 2 — formulário público | Parcial | Percursos por perfil, página/menu públicos, validação, modo avançado, privacidade anónima e navegação por teclado; Edge CDP verificou 390/768/1024/1440 px, foco em 12 destinos nomeados/visíveis, zero controlos sem nome/overflow e GET `/api/v1/simulation-drafts/current` 200 no PostgreSQL/PostGIS QA. Evidência: [relatório browser](simulator-browser-audit-2026-10-03.json), [script reutilizável](../../../tools/simulator-a11y-browser.mjs). | Testes com leitor de ecrã e dispositivo físico, validação manual do percurso e revisão de produto. O smoke test não calcula nem testa submissões de escrita. | +| 3 — calculadoras | Parcial, educativas | Orçamento, break-even, financiamento e amortização; testes exactos cobrem juros zero, taxas nominais/efectivas, carência, comissões, reconciliação em cêntimos e última prestação. | Modelo não é oferta de credor; revisão financeira institucional e cobertura de necessidades pessoais mais ampla. | +| 4 — fontes/governação | Parcial; gate humano pendente | Texto do BR 248 e republicação da Lei 22/2022 arquivados/revistos; ciclo de governação de regras implementado. A taxa geral de IVA de 16% está confirmada no art. 17(1) da republicação oficial. | Regulamentos anunciados de 2026, regras por âmbito/vigência, INSS, pauta integral, revisão e aprovação formal do Fiscalista. INM encaminha BR 186/187 para a Loja; as fichas abertas não expõem o conteúdo. | +| 5 — motor fiscal | Infraestrutura interna parcial; cálculo público bloqueado | DSL whitelist/DAG, aritmética racional, arredondamento explícito, resolvedor de parâmetros por período/contexto e governação versionada; testes focados 9/9 + 4/4. | Integração com RunSimulation e snapshot explicável, fontes oficiais autenticadas, arredondamento aprovado por componente e casos golden revistos por Fiscalista. | +| 6 — plano financeiro | Parcial, implementado em grande parte | Plano empresarial v1–v17; calendário de pagamentos de passivos de abertura, com saldo final exposto; versões anteriores legíveis. Suite financeira/frontend verde. | Validação contabilística institucional e incumprimento/fornecedores partilhados não modelados. | +| 7 — ponte financeira/fiscal | Parcial | Ponte financeira por mês; tesouraria aceita imposto fixo ou calendário variável por pressuposto do utilizador, mantém meses ausentes indeterminados e separa créditos/pedidos/reconhecimentos IVA do caixa. Suite de calendário 11/11. | Calendário local não persistido; classificação de dedutibilidade e regras fiscais aprovadas continuam pendentes. | +| 8 — resultados/exportação | Concluída tecnicamente para CSV; validação global parcial | Ecrã/CSV reconciliam snapshots e hashes; calculadoras, tesouraria, plano e resultado aritmético têm CSV. PDF não é requisito desta etapa no anexo. | Revisão assistiva manual e gates institucionais continuam pendentes. | +| 9 — conta/Project/CRM | Parcial | Histórico paginado autenticado, isolamento, consentimento, optimistic locking e conversão escolhida para rascunho cifrado, com versão/hash e deduplicação. | Não submete/cria projecto no CRM; teste de integração e revisão assistiva do fluxo real pendentes. | +| 10 — incentivos/extensões | Parcial informativa/territorial; benefícios e cálculos regulados desactivados | Zonas públicas exigem fonte vigente, registos validados, relações/geo revistas; interface esclarece que zona não atribui benefício/licença/autorização. InvestmentZoneTest 6/6, 104 asserções. | Inventário validado por domínio; fontes oficiais/autenticidade; regras fiscais/aduaneiras/cambiais e aprovação competente; revisão de produto/browser. | +| 11 — verificação/entrega | Parcial | Regressão PHP integral 253/253 (2.590 asserções), frontend 119/119, TypeScript, PHPStan 0 erros, build Vite 2.397 módulos; testes de zonas 6/6. | Revisão manual com leitor de ecrã/dispositivo, aprovação de produto e gates fiscais/jurídicos. | ## Reconciliação actual das etapas — 2026-10-03 A tabela histórica abaixo é complementada e, para totais/estado técnico, substituída por esta reconciliação mais recente. -| Etapa | Estado actual | Gate/pendência principal | -| --- | --- | --- | -| 0 | Concluída tecnicamente | Revisão de produto. | -| 1 — contratos | Concluída tecnicamente | Round-trip API/persistência verificado nos testes de execução e relatório; conversão/retoma cobertas pelos testes de drafts; guards de respostas consumidas e contratos dos endpoints ainda não consumidos estão testados. Gate global permanece independente. | -| 2 | Parcial | Leitor de ecrã/dispositivo físico e revisão manual após modo fiscal personalizado. | -| 3 | Parcial, educativa | Revisão financeira institucional. | -| 4 | Parcial; fontes legais pendentes | Texto oficial completo dos BR 186/187 e aprovação Fiscalista. | -| 5 — motor fiscal | Infraestrutura interna parcial; cálculo público bloqueado | DSL whitelist/DAG, aritmética racional, arredondamento explícito, resolvedor de parâmetros por período/contexto e governação versionada; testes focados 9/9 + 4/4. | Integração com RunSimulation e snapshot explicável, fontes oficiais autenticadas, arredondamento aprovado por componente e casos golden revistos por Fiscalista. | -| 6–7 | Parciais | Plano v17 reconcilia pagamentos de passivos de abertura; ponte mantém matéria colectável indeterminada sem regra fiscal aprovada. Validação institucional/fornecedores partilhados pendentes. | -| 8 — resultados/exportação | Concluída tecnicamente para CSV; validação global parcial | CSV/ecrã reconciliam; PDF não está no escopo do anexo. Revisão assistiva e gates institucionais continuam pendentes. | -| 9 | Parcial | Resultado concluído mantém manifestação pública independente em `/manifestar-interesse`, sem partilhar a simulação; conversão consentida para rascunho continua separada. Integração/criação de Project e CRM e revisão assistiva continuam pendentes. | -| 10 — incentivos/extensões | Parcial informativa/territorial; benefícios e cálculos regulados desactivados | Zonas públicas exigem fonte vigente, registos validados, relações/geo revistas; interface esclarece que zona não atribui benefício/licença/autorização. InvestmentZoneTest 6/6, 104 asserções. | Inventário validado por domínio; fontes oficiais/autenticidade; regras fiscais/aduaneiras/cambiais e aprovação competente; revisão de produto/browser. | -| 11 — verificação/entrega | Parcial | Regressão PHP integral 253/253 (2.590 asserções), frontend 119/119, TypeScript, PHPStan 0 erros, build Vite 2.397 módulos; testes de zonas 6/6. | Revisão manual com leitor de ecrã/dispositivo, aprovação de produto e gates fiscais/jurídicos. | +| Etapa | Estado actual | Gate/pendência principal | +| ------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Concluída tecnicamente | Revisão de produto. | +| 1 — contratos | Concluída tecnicamente | Round-trip API/persistência verificado nos testes de execução e relatório; conversão/retoma cobertas pelos testes de drafts; guards de respostas consumidas e contratos dos endpoints ainda não consumidos estão testados. Gate global permanece independente. | +| 2 | Parcial | Leitor de ecrã/dispositivo físico e revisão manual após modo fiscal personalizado. | +| 3 | Parcial, educativa | Revisão financeira institucional. | +| 4 | Parcial; fontes legais pendentes | Texto oficial completo dos BR 186/187 e aprovação Fiscalista. | +| 5 — motor fiscal | Infraestrutura interna parcial; cálculo público bloqueado | DSL whitelist/DAG, aritmética racional, arredondamento explícito, resolvedor de parâmetros por período/contexto e governação versionada; testes focados 9/9 + 4/4. | Integração com RunSimulation e snapshot explicável, fontes oficiais autenticadas, arredondamento aprovado por componente e casos golden revistos por Fiscalista. | +| 6–7 | Parciais | Plano v17 reconcilia pagamentos de passivos de abertura; ponte mantém matéria colectável indeterminada sem regra fiscal aprovada. Validação institucional/fornecedores partilhados pendentes. | +| 8 — resultados/exportação | Concluída tecnicamente para CSV; validação global parcial | CSV/ecrã reconciliam; PDF não está no escopo do anexo. Revisão assistiva e gates institucionais continuam pendentes. | +| 9 | Parcial | Resultado concluído mantém manifestação pública independente em `/manifestar-interesse`, sem partilhar a simulação; conversão consentida para rascunho continua separada. Integração/criação de Project e CRM e revisão assistiva continuam pendentes. | +| 10 — incentivos/extensões | Parcial informativa/territorial; benefícios e cálculos regulados desactivados | Zonas públicas exigem fonte vigente, registos validados, relações/geo revistas; interface esclarece que zona não atribui benefício/licença/autorização. InvestmentZoneTest 6/6, 104 asserções. | Inventário validado por domínio; fontes oficiais/autenticidade; regras fiscais/aduaneiras/cambiais e aprovação competente; revisão de produto/browser. | +| 11 — verificação/entrega | Parcial | Regressão PHP integral 253/253 (2.590 asserções), frontend 119/119, TypeScript, PHPStan 0 erros, build Vite 2.397 módulos; testes de zonas 6/6. | Revisão manual com leitor de ecrã/dispositivo, aprovação de produto e gates fiscais/jurídicos. | Evidência global mais recente: PHP 253/253 (2.590 asserções) na última suite QA registada; frontend 124/124, TypeScript, PHPStan (0 erros) na suite QA registada, e build Vite (2.397 módulos) passaram. Esta tranche repetiu frontend/TypeScript/build, mas não a suite PHP. Smoke Edge QA passou. PostgreSQL QA/55441 parado; nenhuma regra fiscal activada. + ## Decisões e limites actuais - **IVA:** a divergência legal 16%/17% fica resolvida ao nível textual pela republicação oficial da Lei 22/2022, art. 17(1); páginas informativas da AT que dizem 17% estão desactualizadas. Isto não aprova regimes, incidência, deduções, reembolsos, regras regulamentares de 2026 nem parâmetros de produção. @@ -85,7 +86,7 @@ Prosseguir testes/validação financeira independente e revisão manual de acess - O formulário valida respostas paginadas do histórico, leituras de rascunho (incluindo `data: null` e `personal_snapshot: []` anónimo), confirmações de escrita e anexação. Erros estruturais não são aceites como sucesso. - OpenAPI liga os schemas de questionário, histórico, rascunho, anexação e relatório aos endpoints correspondentes; respostas de erro e paginação estão descritas. As decimais continuam strings exactas nos pedidos. -- Evidência: frontend 102/102; `npm.cmd run types:check`, lint dirigido, JSON/OpenAPI com refs locais resolvidos, build 2.388 módulos e `git diff --check` passaram. Aviso apenas informativo de dependência opcional `fontaine`. +- Evidência: frontend 102/102; `npm.cmd run types:check`, lint dirigido, JSON/OpenAPI com refs locais resolvidos, build 2.388 módulos e `git diff --check` passaram. Aviso apenas informativo de dependência opcional `fontaine`. - A Etapa 1 continua parcial: jobs, formatos detalhados de erros, restantes round-trips de persistência e contrato/runtime de endpoints que o formulário ainda não consome carecem de revisão e testes específicos. ### Adenda da Etapa 1 — jobs de execução e validação final (2026-10-03) @@ -230,9 +231,11 @@ Verificação desta extensão: `php artisan test --filter=SimulationTaxParameter ### Revalidacao da regressao do simulador — 2026-10-03 O primeiro preflight ocorreu com o PostgreSQL QA desligado e nao chegou aos casos. Essa condicao foi resolvida pela criacao de cluster descartavel; o resultado final (250/250) e a execucao em Edge estao registados nas adendas imediatamente seguintes. A suite frontend passou 115/115, TypeScript e PHPStan (0 erros), e build (2.397 modulos) passaram. O gate da Etapa 11 continua parcial apenas para leitor de ecrã/dispositivo e revisao humana de produto. Nenhuma regra fiscal foi activada. + ### Regressao PHP global no worktree actual — 2026-10-03 Actualizacao que substitui a nota anterior de que a suite PHP nao executou: foi criado um cluster PostgreSQL 17.11 temporario e isolado em `%TEMP%\investmoz-goalqa-20261003-1546`, limitado a `127.0.0.1:55441`. `php artisan test` passou 250/250 testes e 2.582 assertions, incluindo 3/3 casos DUAT (38 assertions). PostgreSQL foi parado e 55441 ficou sem listener. A porta 5432 nao foi utilizada. O ensaio encontrou e levou a correcao da validacao de consentimento para rascunhos incompletos. Node frontend 115/115, TypeScript, PHPStan (0 erros), Pint dirigido e build (2.397 modulos) passaram. Continuam pendentes validacao manual assistiva/de produto e os gates fiscais humanos; nao houve activacao de regras. + ### Revalidacao Edge do simulador — Etapas 2/11 — 2026-10-03 Smoke de browser no Edge headless passou com alvo verificado `investmoz_test:55441`; quatro larguras (390, 768, 1024, 1440), 12 focos sequenciais nomeados/visiveis, sem overflow, controlo sem nome ou excepcoes JavaScript. A unica chamada API foi GET `/api/v1/simulation-drafts/current` 200. QA ficou em 0 simulacoes/0 rascunhos e os processos Edge/PHP/PostgreSQL temporarios foram encerrados, com portas 55441/18089/9224 fechadas. Relatorio: [simulator-browser-audit-2026-10-03-rerun-runtime-verified.json](simulator-browser-audit-2026-10-03-rerun-runtime-verified.json). Isto reforca validacao automatizada, mas nao substitui leitor de ecrã real, dispositivo fisico ou revisao de produto. @@ -274,15 +277,19 @@ O CSV empresarial deixou de afirmar falsamente que todos os valores foram digita ### Etapa 9 — conversão consentida para rascunho (2026-10-03) A conversão existente foi reconciliada com os critérios de consentimento explícito, autorização, snapshot/hash, preservação do original, idempotência, ETag e revogação; suite feature passou 10/10 (79 asserções) em PostgreSQL 17 isolado na porta 55441. A conversão só anexa ao rascunho: Project, submissão formal e CRM não são criados. O gate permanece parcial até existir evidência desses fluxos e revisão manual assistiva/produto. Ver `stage-9-technical-traceability.md`. + ### Reconciliação do motor fiscal — 2026-10-03 O gate histórico da Etapa 10 foi actualizado à luz do código actual: avaliador DSL, DAG, precisão racional, resolução de parâmetros e governação já existem e passaram testes focados. O motor permanece fora do percurso de cálculo público; nenhuma regra é activada. Ver `stage-10-technical-traceability.md` para os critérios e limites. + ### Etapa 10 — extensão informativa territorial (2026-10-03) A auditoria confirmou controlos existentes para zonas, fontes/vigência, relações, geometrias e origem de pedidos, e verificou a advertência pública de que associação territorial não atribui benefícios, licenças ou autorização aduaneira. `InvestmentZoneTest` passou 6/6 (104 asserções) em PostgreSQL/PostGIS QA isolado. Benefícios fiscais, cálculo aduaneiro/cambial e decisões regulatórias continuam sem activação. Ver `stage-10-extension-traceability.md`. + ### Regressão integral actualizada — 2026-10-03 `php artisan test` passou 253/253 testes (2.590 asserções) no PostgreSQL 17/PostGIS QA descartável, porta 55441, depois de repetir com o administrador local do cluster porque três testes RLS criam um papel de teste e o login não-superuser não tem `CREATEROLE`. A primeira tentativa com esse login foi 250/253 e falhou apenas nesses três setups; a execução integral seguinte passou. Frontend 119/119; TypeScript; PHPStan 0 erros; build Vite 2.397 módulos. A build avisa que `fontaine` opcional não está instalado. QA foi encerrado; 5432 não foi usada. + ### Etapa 7 — calendário mensal de pressupostos de tesouraria (2026-10-03) Foi acrescentado um modo de calendário variável de imposto mensal ao lado do valor fixo mensal existente. Cada montante é um pressuposto do utilizador; mês vazio permanece indeterminado, o saldo completo propaga a incerteza a partir desse mês, e a caixa conhecida desconta os impostos informados. CSV e interface esclarecem que não é apuramento legal. Teste mensal 11/11 e suite frontend 123/123 passaram; TypeScript e `vp check` dirigido passaram. O calendário não é guardado em snapshot de conta. Ver `stage-7-technical-traceability.md`. diff --git a/docs/investmoz/simulations/data-contracts.md b/docs/investmoz/simulations/data-contracts.md index f7429618..68fdb9fa 100644 --- a/docs/investmoz/simulations/data-contracts.md +++ b/docs/investmoz/simulations/data-contracts.md @@ -71,5 +71,6 @@ O contexto v1 inclui `scenario_version`, correspondente a `SimulationReport.vers - Snapshot `business-plan-v16`: supplier orders can declare up to 12 payment installments, each with a 0-12 month lag and a percentage of the order. Declared installment percentages cannot exceed 100%; cent rounding is capped at the unpaid order balance. Percentages below 100% leave an explicit unallocated amount due, and installments beyond the forecast horizon remain outstanding. Legacy v15 supplier terms are upgraded to one 100% installment when serialized; stored v1-v15 snapshots remain readable without changing their historical fields. Novas execucoes identificam-se como `portal-arithmetic-v1`, para nao sugerir que o motor fiscal esta activo. Snapshots historicos com `portal-tax-arithmetic-v1` mantem os valores e continuam legiveis; resultados guardados nao sao reescritos. + - Snapshot `business-plan-v17`: adds `openingPayablesInstallments`, an optional schedule with month and percent of the original opening payable balance. Declared percentages sum to at most 100%; cent rounding is capped at the unpaid balance. Scheduled settlements reduce cash and opening payables, not profit or period expenses. Any unpaid amount remains a closing payable; an absent schedule assumes no settlement and leaves the full opening amount outstanding. Snapshots v1-v16 remain readable with an empty schedule; explicit resave produces v17. - Execuções API concluídas só são aceites pelo consumidor se `result_snapshot` for um envelope `contract_version: 1` válido (incluindo consistência entre `result.value` e `indicative_total`) ou um snapshot legado legível com montante decimal. Estados concluídos sem snapshot, com snapshot malformado ou com versão v1 inválida são rejeitados na fronteira da API; estados não concluídos podem manter snapshot ausente/nulo. Snapshots históricos legíveis continuam compatíveis. diff --git a/docs/investmoz/simulations/entrepreneur-question-mapping-2026-10-05.md b/docs/investmoz/simulations/entrepreneur-question-mapping-2026-10-05.md index d00e902d..c8b2e0ed 100644 --- a/docs/investmoz/simulations/entrepreneur-question-mapping-2026-10-05.md +++ b/docs/investmoz/simulations/entrepreneur-question-mapping-2026-10-05.md @@ -4,32 +4,32 @@ Data: 05/10/2026. Os dois DOCX de referência foram lidos no caminho indicado pe ## Inventário funcional reutilizado -| Capacidade existente | Localização | Uso nesta melhoria | -|---|---|---| -| Entrada ideia/investidor e secções dinâmicas | `resources/js/lib/simulation-form.ts`, `resources/js/components/simulation-form.tsx` | Reordenada em seis etapas para a ideia; o percurso de projecto definido mantém detalhe próprio. | -| Rascunho local e recuperação cifrada no servidor; associação explícita à conta | `SimulationForm`, `SimulationFormDrafts`, `StoreSimulationFormDraftRequest` | Mantidos; campos novos pertencem ao mesmo snapshot económico. | -| País, actividade, província/distrito/posto/localidade e zonas | campos pesquisáveis existentes e catálogos API | Mantidos; seleção de zona não é prova de infraestrutura, autorização ou incentivo. | -| Plano financeiro, cenários e ferramentas financeiras | `simulation-business-plan.tsx`, `simulation-financial-tools.tsx`, `simulation-cashflow-planner.tsx` | Reutilizados; fórmulas e snapshots guardados não foram alterados. | -| Equipa/competências e marcos | `project-planning.tsx` | Mostrados juntos numa etapa, sem duplicar os respectivos snapshots. | -| Revisão e DOCX/PDF/CSV/ZIP | `project-dossier-export.tsx`, `project-dossier-report.ts` | Mantidos; resumo, mercado, riscos e respostas entram no mesmo relatório/CSV. | +| Capacidade existente | Localização | Uso nesta melhoria | +| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| Entrada ideia/investidor e secções dinâmicas | `resources/js/lib/simulation-form.ts`, `resources/js/components/simulation-form.tsx` | Reordenada em seis etapas para a ideia; o percurso de projecto definido mantém detalhe próprio. | +| Rascunho local e recuperação cifrada no servidor; associação explícita à conta | `SimulationForm`, `SimulationFormDrafts`, `StoreSimulationFormDraftRequest` | Mantidos; campos novos pertencem ao mesmo snapshot económico. | +| País, actividade, província/distrito/posto/localidade e zonas | campos pesquisáveis existentes e catálogos API | Mantidos; seleção de zona não é prova de infraestrutura, autorização ou incentivo. | +| Plano financeiro, cenários e ferramentas financeiras | `simulation-business-plan.tsx`, `simulation-financial-tools.tsx`, `simulation-cashflow-planner.tsx` | Reutilizados; fórmulas e snapshots guardados não foram alterados. | +| Equipa/competências e marcos | `project-planning.tsx` | Mostrados juntos numa etapa, sem duplicar os respectivos snapshots. | +| Revisão e DOCX/PDF/CSV/ZIP | `project-dossier-export.tsx`, `project-dossier-report.ts` | Mantidos; resumo, mercado, riscos e respostas entram no mesmo relatório/CSV. | ## Correspondência temática -| Tema/perguntas dos DOCX | Pergunta adaptada no simulador | Campos/componentes do dossier | Tratamento | -|---|---|---|---| -| Objectivos, missão, proposta e descrição | Que necessidade pretende resolver? Que solução propõe? | `project_title`, `project_summary`, `project_problem`, `value_proposition` | Texto declarado pelo utilizador; nenhum objectivo ou missão do exemplo foi importado. | -| Produto/serviço e actividade | O que pretende oferecer? Em que actividade se enquadra? | `products_services`, `sector_id`, `activity_id`, `project_type`, `maturity_stage` | Catálogo de actividade existente; conteúdo só é exibido se preenchido. | -| Mercado, segmentos e clientes | Quem são os clientes, onde estão e que necessidade têm? Já conversou com eles? | `target_customers`, `customer_validation_status`, `market_assumption`, `market_source` | Estados Sim/Não/Parcialmente/Ainda não sei/Não se aplica não substituem descrição nem fonte. | -| Concorrência e padrões de compra | Que alternativas usam hoje os clientes? | `competitor_assumption` | Hipótese declarada, sem estimativas de dimensão de mercado. | -| Estratégia comercial, vendas e presença digital | Como chegará aos clientes? Que divulgação pretende testar? | `sales_channel_assumption`, `marketing_approach` | Planeamento do utilizador; não é previsão de vendas. | -| SWOT, ameaças e respostas | O que falta esclarecer? Que acção pretende tomar? | `ProjectReadiness`, `project_risk_responses_snapshot` | Alertas determinísticos, sem probabilidade, perda monetária ou pontuação de gravidade. | -| Localização, instalações e recursos | Já avaliou a adequação do local? Por que o escolheu? | `province_id`, `district_id`, `admin_post_id`, `locality_id`, `location_assessment_status`, `location_rationale`, `infrastructure_needs`, `land_status` | Relações da base territorial; dados não disponíveis não são inventados. | -| Gestão, equipa e pessoal | Que funções/competências estão disponíveis ou em falta? Que resposta, responsável, custo e prazo planeia? | `ProjectTeamPlanner`, `project_team_snapshot` | Perguntas moralizantes sobre personalidade, confiança, liderança ou capacidade pessoal foram excluídas. | -| Marcos e execução | Que resultado pretende alcançar e quando? | `ProjectMilestonePlanner`, `project_execution_snapshot` | Planeado e realizado permanecem separados; um marco existente pode preencher uma resposta de risco. | -| Arranque, custos e projecções | Já identificou custos, moeda, periodicidade, recebimentos e financiamento? | plano financeiro versionado, fluxo de caixa, `cost_estimate_status`, campos financeiros existentes | Não copiadas taxas, margens, crescimento, datas ou previsões do template. Vendas, recebimentos, dívida, juros e amortização continuam categorias distintas nas ferramentas existentes. | -| Capital e financiamento | Qual o estado dos recursos de financiamento necessários? | capital próprio, dívida, montante mobilizado/pretendido, `funding_status`, necessidade de negócio | Sem catálogo de produtos documentado, pré-elegibilidade continua indeterminada. | -| Formalização, forma jurídica, marcas, patentes e documentos | Consultar orientação quando a maturidade/actividade tornar o assunto relevante | Guias existentes e `required_documents` dos fluxos formais | Não pedir NUIT, identidade, registo ou carregamento de documentos para explorar uma ideia; respostas legais do questionário antigo não viram aconselhamento. | -| Balanço, rácios, break-even, forecast de três anos | Usar apenas as ferramentas e saídas já suportadas | business plan, calculadores existentes e resultados versionados | Não geradas demonstrações financeiras nem indicadores sem motor/dados suficientes. | +| Tema/perguntas dos DOCX | Pergunta adaptada no simulador | Campos/componentes do dossier | Tratamento | +| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Objectivos, missão, proposta e descrição | Que necessidade pretende resolver? Que solução propõe? | `project_title`, `project_summary`, `project_problem`, `value_proposition` | Texto declarado pelo utilizador; nenhum objectivo ou missão do exemplo foi importado. | +| Produto/serviço e actividade | O que pretende oferecer? Em que actividade se enquadra? | `products_services`, `sector_id`, `activity_id`, `project_type`, `maturity_stage` | Catálogo de actividade existente; conteúdo só é exibido se preenchido. | +| Mercado, segmentos e clientes | Quem são os clientes, onde estão e que necessidade têm? Já conversou com eles? | `target_customers`, `customer_validation_status`, `market_assumption`, `market_source` | Estados Sim/Não/Parcialmente/Ainda não sei/Não se aplica não substituem descrição nem fonte. | +| Concorrência e padrões de compra | Que alternativas usam hoje os clientes? | `competitor_assumption` | Hipótese declarada, sem estimativas de dimensão de mercado. | +| Estratégia comercial, vendas e presença digital | Como chegará aos clientes? Que divulgação pretende testar? | `sales_channel_assumption`, `marketing_approach` | Planeamento do utilizador; não é previsão de vendas. | +| SWOT, ameaças e respostas | O que falta esclarecer? Que acção pretende tomar? | `ProjectReadiness`, `project_risk_responses_snapshot` | Alertas determinísticos, sem probabilidade, perda monetária ou pontuação de gravidade. | +| Localização, instalações e recursos | Já avaliou a adequação do local? Por que o escolheu? | `province_id`, `district_id`, `admin_post_id`, `locality_id`, `location_assessment_status`, `location_rationale`, `infrastructure_needs`, `land_status` | Relações da base territorial; dados não disponíveis não são inventados. | +| Gestão, equipa e pessoal | Que funções/competências estão disponíveis ou em falta? Que resposta, responsável, custo e prazo planeia? | `ProjectTeamPlanner`, `project_team_snapshot` | Perguntas moralizantes sobre personalidade, confiança, liderança ou capacidade pessoal foram excluídas. | +| Marcos e execução | Que resultado pretende alcançar e quando? | `ProjectMilestonePlanner`, `project_execution_snapshot` | Planeado e realizado permanecem separados; um marco existente pode preencher uma resposta de risco. | +| Arranque, custos e projecções | Já identificou custos, moeda, periodicidade, recebimentos e financiamento? | plano financeiro versionado, fluxo de caixa, `cost_estimate_status`, campos financeiros existentes | Não copiadas taxas, margens, crescimento, datas ou previsões do template. Vendas, recebimentos, dívida, juros e amortização continuam categorias distintas nas ferramentas existentes. | +| Capital e financiamento | Qual o estado dos recursos de financiamento necessários? | capital próprio, dívida, montante mobilizado/pretendido, `funding_status`, necessidade de negócio | Sem catálogo de produtos documentado, pré-elegibilidade continua indeterminada. | +| Formalização, forma jurídica, marcas, patentes e documentos | Consultar orientação quando a maturidade/actividade tornar o assunto relevante | Guias existentes e `required_documents` dos fluxos formais | Não pedir NUIT, identidade, registo ou carregamento de documentos para explorar uma ideia; respostas legais do questionário antigo não viram aconselhamento. | +| Balanço, rácios, break-even, forecast de três anos | Usar apenas as ferramentas e saídas já suportadas | business plan, calculadores existentes e resultados versionados | Não geradas demonstrações financeiras nem indicadores sem motor/dados suficientes. | ## Versões e compatibilidade diff --git a/docs/investmoz/simulations/fiscal-reform-2026-review.md b/docs/investmoz/simulations/fiscal-reform-2026-review.md index 22571a7c..ac71ca66 100644 --- a/docs/investmoz/simulations/fiscal-reform-2026-review.md +++ b/docs/investmoz/simulations/fiscal-reform-2026-review.md @@ -26,24 +26,24 @@ Fonte: [ficha INM do BR n.º 89/2017](https://www.inm.gov.mz/pt-br/content/assem As indicações secundárias foram confrontadas com o artigo da Tempo Content Manager sobre reforma fiscal 2026, com a nota KPMG “Mozambique Tax Updates” de 07-01-2026 para IRPS e com a nota DLA Piper Africa/SAL & Caldeira sobre a Lei 8/2025; a orientação antiga de microfinanças é a página/checklist do BM de 2022, que hoje aparece marcada “To be updated”. Em 2026-10-04 foi localizado no acervo do proprietário o documento `Informacao Publica/Legislacao - resumos/Reforma fiscal de 2026.docx`; a afirmação anterior de indisponibilidade estava limitada ao repositório e caminho pesquisados e fica substituída. O documento é fonte secundária; ainda requer reconciliação artigo a artigo com os diplomas oficiais, e a sua localização não activa regras. -| Imposto/tema | Alteração indicada | Fonte secundária | Diploma/artigos identificados | Vigência | Componente afectada | Testes necessários | Aprovação | -| --- | --- | --- | --- | --- | --- | --- | --- | -| ICE | Extensão das taxas da tabela até 2027 | Tempo | Lei 7/2025, art. 1; altera Lei 19/2022 | Aplicação 2026–2027 conforme publicação; confirmar transição por produto | ICE/Importação: código, unidade, origem, data | Antes/depois; isenção; taxa específica/ad valorem; acumulações | Texto identificado; tabela por produto e Fiscalista pendentes | -| Aduaneiro | Alterações às Instruções Preliminares, ZCLCA e mercadorias | DLA Piper Africa/SAL & Caldeira; Tempo | Lei 8/2025, art. 1: art. 21(2), arts. 22 e 27 da Lei 17/2022; vigência no art. 2 | 01-01-2026 conforme publicação consultada; transições por mercadoria por validar | Código pautal, origem, preferência, unidade, valor e data | Código obrigatório; origem/preferência; calendário; código ausente => estimativa incompleta | Sem taxa genérica; validar classificação e tabela oficial por código | -| ISPC — âmbito | Regime revisto; alegada eliminação confundida com IVA; novo regulamento ISPC aprovado pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 9/2025, arts. 2–5; regulamento anunciado em 30-09-2026, número/artigos ainda por identificar no BR | Lei com vigência indicada a partir de 01-01-2026; vigência do regulamento pendente de leitura do BR n.º 186/187 | Actividade, receita, estabelecimentos, tipo/duração de serviço e opção | Limite MZN 4m; actividades/exclusões; multiestabelecimentos; participações; serviços >183 dias; casos de transição | Não activar: falta confirmar texto publicado, vigência e critérios operacionais | -| ISPC — taxas e obrigações | Escalões 3/4/5%, certos serviços 12%, profissões liberais 15%, excedente 20%; trimestre/facturação | Tempo; PwC Moçambique | Lei 9/2025, arts. 8–12 | 2026, sujeita a transição/regulamento | Actividade e periodicidade trimestral | Limites MZN 1m, 2,5m, 4m; zero; actividades cumulativas; MZN 500; declaração; retenção em certas compras | Taxas no texto; regra marginal/total e aplicação carecem interpretação aprovada | -| ISPC — profissão liberal | Limite temporal de cinco anos | PwC Moçambique | Lei 9/2025, arts. 9, 9-A e transição | Contagem individual/transitória a confirmar | Actividade e início/histórico do regime | Limite temporal; transição IRPS/IRPC | Não activar sem regulamento e validação do Fiscalista | -| IVA — regimes | Revogação de regimes simplificados confundida com fim do ISPC; alterações regulamentares aprovadas pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 10/2025, art. 3 (revogações), art. 53 (transição); Lei 9/2025 separadamente; decreto regulamentar por identificar no BR | Alterações legais para 2026; vigência/transição regulamentar por confirmar no BR n.º 186/187 | Escolha IVA separada do ISPC | Antigo regime IVA; ISPC; combinações/transição; operações digitais e não residentes | Divergência resolvida; conteúdo e vigência do novo regulamento pendentes | -| IVA — digital/não residente | Serviços/bens digitais transfronteiriços e obrigações de adquirente | Tempo; PwC Moçambique | Lei 10/2025, arts. 3, 6, 26, 32–33 (texto consolidado a conferir) | 2026; regulamento anunciado ainda não confirmado no BR | Tipo da operação, estabelecimento, não residente | Operação interna/externa; autoliquidação; obrigação e prazo por perfil | Base identificada; fluxo aguarda regulamento consolidado | -| IVA — dedução, crédito e reembolso | Dedução com documento válido; crédito pode ser pedido, reconhecido ou pago | Tempo; PwC Moçambique | Lei 10/2025, arts. 18, 19, 21, 23, 25-A, 27, 32–33; Decreto 52/2025, a reconciliar com a versão consolidada | 2026, processo conforme regulamento | Estado separado: estimativa, pedido, reconhecido, recebido; estabelecimento e documentação | Factura válida/ausente; dedução parcial; crédito; pedido; suspensão; reconhecimento; pagamento | Não tratar crédito como liquidez. Processo/prazos operacionais por validar | -| IRPS — residência | Nacionalidade ou 180 dias tratados como critério único | KPMG; PwC Portugal | Lei 11/2025, art. 1, nova redacção do art. 21 do Código IRPS | 01-01-2026 conforme lei | Residência auto-indicada ou “não sei”; nunca inferida por nacionalidade/dias isolados | Factores legais e caso indeterminado | Critérios alterados; determinação automática não implementada | -| IRPS — rendimentos | Inclusão de rendimentos digitais, prediais e deduções documentadas | KPMG; PwC Portugal | Lei 11/2025, art. 1, alterações aos arts. 22, 48, 50 e correlatos | 01-01-2026 | Categoria trabalho/predial/digital/mais-valia/outro; documentação sem upload | Fonte digital, despesas elegíveis, documentação, limites e perdas | Requer análise por tipo, regulamento e casos aprovados | -| IRPS — trabalho/mais-valias | Retenção mensal confundida com apuramento anual; mais-valias alegadas a 32% plana | KPMG; PwC Portugal | Lei 11/2025, art. 1, arts. 54-A, 57, 65, 65-A | 01-01-2026 | Distinguir salário líquido mensal estimado, retenção mensal e apuramento anual | Limites antes/depois, deduções, variáveis, retenção vs apuramento | Divergência resolvida: não generalizar 32% plana de IRPC para IRPS; Fiscalista pendente | -| IRPC — estabelecimento permanente | Serviços/construção >90 dias sem ressalvas nos resumos | Tempo; PwC Moçambique | Lei 12/2025, art. 1, art. 3(2)(b)-(c) | 01-01-2026 conforme lei | Tipo de serviço, local/presença, período | Construção; serviços agregados em 12 meses; ressalva de serviços digitais | Ressalva identificada; tratados/regulamento e teste pendentes | -| IRPC — digital, retenção e ganhos | Rendimento digital, retenção 10%, reconhecimento de custo, taxa autónoma 32% | Tempo; PwC Moçambique | Lei 12/2025, art. 1, arts. 5, 20, 61-A, 62, 67 e 75 | 01-01-2026; regulamentação contabilística por confirmar | Natureza, residência, estabelecimento, tipo e momento de pagamento/reconhecimento | Origem digital; agente; evento de retenção; ganhos e tratados | Texto identificado; âmbito e exclusões dependem de validação | -| IRPC — contabilidade e procedimentos | Informatização/transição para contabilidade organizada; alterações regulamentares aprovadas pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 12/2025, art. 75(10) e art. 4 transitório; regulamento IRPC aprovado, decreto/artigos por confirmar no BR | Transição legal conforme texto; datas e procedimentos regulamentares pendentes de leitura do BR n.º 186/187 | Apenas intake/planeamento; não certificar conformidade | Datas de transição, formatos, sujeitos e obrigações acessórias | Pendente do texto publicado e da análise de vigência/transição | -| INSS | Resumos não substituem regime contributivo | Não identificada no pacote recebido | Decreto 51/2017 e alterações, incluindo Decreto 56/2024 a conferir no INSS | Por período e contribuinte | Empregador/trabalhador/TCP, remuneração e período | Base, exclusões, taxas e mudança de vigência | Não reconciliado; não calcular nem assumir 4%/3% | -| Microfinanças | Checklist antigo cita Decreto 57/2004, capitais fixos, membros e prazos; o BM abriu actualização cadastral dos operadores registados entre 11-09 e 10-10-2026 | Guia/checklist BM de 2022 (marcado “Informação em actualização”); comunicado cadastral BM de 11-09-2026 | Lei 20/2020; Decreto 50/2024, arts. 157–170; art. 2 revoga Decreto 57/2004; Aviso 7/GBM/2017 continua listado pelo BM, mas isso não confirma sozinho alcance, vigência nem compatibilidade após o regulamento | Decreto publicado em 2024; aviso cadastral de 2026 tem prazo próprio até 10-10-2026, não altera por si só os requisitos substantivos; lista pública ainda identificada como reportada a 31-03-2025 | Calculadora educativa de empréstimo separada de qualquer futura preparação documental/registo | Requisitos por OPE, operador de microcrédito, microbanco/cooperativa; capitais/limites e procedimentos BM vigentes; efeitos da actualização cadastral de 2026 | Não reutilizar requisitos do guia antigo. Confirmar Avisos aplicáveis e resultado da actualização cadastral antes de documentar um percurso de registo | +| Imposto/tema | Alteração indicada | Fonte secundária | Diploma/artigos identificados | Vigência | Componente afectada | Testes necessários | Aprovação | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ICE | Extensão das taxas da tabela até 2027 | Tempo | Lei 7/2025, art. 1; altera Lei 19/2022 | Aplicação 2026–2027 conforme publicação; confirmar transição por produto | ICE/Importação: código, unidade, origem, data | Antes/depois; isenção; taxa específica/ad valorem; acumulações | Texto identificado; tabela por produto e Fiscalista pendentes | +| Aduaneiro | Alterações às Instruções Preliminares, ZCLCA e mercadorias | DLA Piper Africa/SAL & Caldeira; Tempo | Lei 8/2025, art. 1: art. 21(2), arts. 22 e 27 da Lei 17/2022; vigência no art. 2 | 01-01-2026 conforme publicação consultada; transições por mercadoria por validar | Código pautal, origem, preferência, unidade, valor e data | Código obrigatório; origem/preferência; calendário; código ausente => estimativa incompleta | Sem taxa genérica; validar classificação e tabela oficial por código | +| ISPC — âmbito | Regime revisto; alegada eliminação confundida com IVA; novo regulamento ISPC aprovado pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 9/2025, arts. 2–5; regulamento anunciado em 30-09-2026, número/artigos ainda por identificar no BR | Lei com vigência indicada a partir de 01-01-2026; vigência do regulamento pendente de leitura do BR n.º 186/187 | Actividade, receita, estabelecimentos, tipo/duração de serviço e opção | Limite MZN 4m; actividades/exclusões; multiestabelecimentos; participações; serviços >183 dias; casos de transição | Não activar: falta confirmar texto publicado, vigência e critérios operacionais | +| ISPC — taxas e obrigações | Escalões 3/4/5%, certos serviços 12%, profissões liberais 15%, excedente 20%; trimestre/facturação | Tempo; PwC Moçambique | Lei 9/2025, arts. 8–12 | 2026, sujeita a transição/regulamento | Actividade e periodicidade trimestral | Limites MZN 1m, 2,5m, 4m; zero; actividades cumulativas; MZN 500; declaração; retenção em certas compras | Taxas no texto; regra marginal/total e aplicação carecem interpretação aprovada | +| ISPC — profissão liberal | Limite temporal de cinco anos | PwC Moçambique | Lei 9/2025, arts. 9, 9-A e transição | Contagem individual/transitória a confirmar | Actividade e início/histórico do regime | Limite temporal; transição IRPS/IRPC | Não activar sem regulamento e validação do Fiscalista | +| IVA — regimes | Revogação de regimes simplificados confundida com fim do ISPC; alterações regulamentares aprovadas pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 10/2025, art. 3 (revogações), art. 53 (transição); Lei 9/2025 separadamente; decreto regulamentar por identificar no BR | Alterações legais para 2026; vigência/transição regulamentar por confirmar no BR n.º 186/187 | Escolha IVA separada do ISPC | Antigo regime IVA; ISPC; combinações/transição; operações digitais e não residentes | Divergência resolvida; conteúdo e vigência do novo regulamento pendentes | +| IVA — digital/não residente | Serviços/bens digitais transfronteiriços e obrigações de adquirente | Tempo; PwC Moçambique | Lei 10/2025, arts. 3, 6, 26, 32–33 (texto consolidado a conferir) | 2026; regulamento anunciado ainda não confirmado no BR | Tipo da operação, estabelecimento, não residente | Operação interna/externa; autoliquidação; obrigação e prazo por perfil | Base identificada; fluxo aguarda regulamento consolidado | +| IVA — dedução, crédito e reembolso | Dedução com documento válido; crédito pode ser pedido, reconhecido ou pago | Tempo; PwC Moçambique | Lei 10/2025, arts. 18, 19, 21, 23, 25-A, 27, 32–33; Decreto 52/2025, a reconciliar com a versão consolidada | 2026, processo conforme regulamento | Estado separado: estimativa, pedido, reconhecido, recebido; estabelecimento e documentação | Factura válida/ausente; dedução parcial; crédito; pedido; suspensão; reconhecimento; pagamento | Não tratar crédito como liquidez. Processo/prazos operacionais por validar | +| IRPS — residência | Nacionalidade ou 180 dias tratados como critério único | KPMG; PwC Portugal | Lei 11/2025, art. 1, nova redacção do art. 21 do Código IRPS | 01-01-2026 conforme lei | Residência auto-indicada ou “não sei”; nunca inferida por nacionalidade/dias isolados | Factores legais e caso indeterminado | Critérios alterados; determinação automática não implementada | +| IRPS — rendimentos | Inclusão de rendimentos digitais, prediais e deduções documentadas | KPMG; PwC Portugal | Lei 11/2025, art. 1, alterações aos arts. 22, 48, 50 e correlatos | 01-01-2026 | Categoria trabalho/predial/digital/mais-valia/outro; documentação sem upload | Fonte digital, despesas elegíveis, documentação, limites e perdas | Requer análise por tipo, regulamento e casos aprovados | +| IRPS — trabalho/mais-valias | Retenção mensal confundida com apuramento anual; mais-valias alegadas a 32% plana | KPMG; PwC Portugal | Lei 11/2025, art. 1, arts. 54-A, 57, 65, 65-A | 01-01-2026 | Distinguir salário líquido mensal estimado, retenção mensal e apuramento anual | Limites antes/depois, deduções, variáveis, retenção vs apuramento | Divergência resolvida: não generalizar 32% plana de IRPC para IRPS; Fiscalista pendente | +| IRPC — estabelecimento permanente | Serviços/construção >90 dias sem ressalvas nos resumos | Tempo; PwC Moçambique | Lei 12/2025, art. 1, art. 3(2)(b)-(c) | 01-01-2026 conforme lei | Tipo de serviço, local/presença, período | Construção; serviços agregados em 12 meses; ressalva de serviços digitais | Ressalva identificada; tratados/regulamento e teste pendentes | +| IRPC — digital, retenção e ganhos | Rendimento digital, retenção 10%, reconhecimento de custo, taxa autónoma 32% | Tempo; PwC Moçambique | Lei 12/2025, art. 1, arts. 5, 20, 61-A, 62, 67 e 75 | 01-01-2026; regulamentação contabilística por confirmar | Natureza, residência, estabelecimento, tipo e momento de pagamento/reconhecimento | Origem digital; agente; evento de retenção; ganhos e tratados | Texto identificado; âmbito e exclusões dependem de validação | +| IRPC — contabilidade e procedimentos | Informatização/transição para contabilidade organizada; alterações regulamentares aprovadas pelo Conselho de Ministros | Tempo; PwC Moçambique | Lei 12/2025, art. 75(10) e art. 4 transitório; regulamento IRPC aprovado, decreto/artigos por confirmar no BR | Transição legal conforme texto; datas e procedimentos regulamentares pendentes de leitura do BR n.º 186/187 | Apenas intake/planeamento; não certificar conformidade | Datas de transição, formatos, sujeitos e obrigações acessórias | Pendente do texto publicado e da análise de vigência/transição | +| INSS | Resumos não substituem regime contributivo | Não identificada no pacote recebido | Decreto 51/2017 e alterações, incluindo Decreto 56/2024 a conferir no INSS | Por período e contribuinte | Empregador/trabalhador/TCP, remuneração e período | Base, exclusões, taxas e mudança de vigência | Não reconciliado; não calcular nem assumir 4%/3% | +| Microfinanças | Checklist antigo cita Decreto 57/2004, capitais fixos, membros e prazos; o BM abriu actualização cadastral dos operadores registados entre 11-09 e 10-10-2026 | Guia/checklist BM de 2022 (marcado “Informação em actualização”); comunicado cadastral BM de 11-09-2026 | Lei 20/2020; Decreto 50/2024, arts. 157–170; art. 2 revoga Decreto 57/2004; Aviso 7/GBM/2017 continua listado pelo BM, mas isso não confirma sozinho alcance, vigência nem compatibilidade após o regulamento | Decreto publicado em 2024; aviso cadastral de 2026 tem prazo próprio até 10-10-2026, não altera por si só os requisitos substantivos; lista pública ainda identificada como reportada a 31-03-2025 | Calculadora educativa de empréstimo separada de qualquer futura preparação documental/registo | Requisitos por OPE, operador de microcrédito, microbanco/cooperativa; capitais/limites e procedimentos BM vigentes; efeitos da actualização cadastral de 2026 | Não reutilizar requisitos do guia antigo. Confirmar Avisos aplicáveis e resultado da actualização cadastral antes de documentar um percurso de registo | ## Alterações no formulário e limites do produto @@ -68,20 +68,18 @@ As indicações secundárias foram confrontadas com o artigo da Tempo Content Ma A pasta de prompts e o acervo de legislação em `C:\Mac\Home\Documents\TEMPO\Portal Tempo` estão acessíveis. A verificação abaixo distingue existência do ficheiro, camada de texto e validação jurídica; não afirma revisão integral de todos os documentos dessa árvore. -| Documento localizado | Evidência técnica | Limite actual | -| --- | --- | --- | -| `Legislacao/Lei 13_2016_Reimpressao Codigo do IVA.pdf` | 2.127.554 bytes; SHA-256 `b90dc7c14ece2a2e3784c62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b`; 32 páginas | Sem camada de texto detectável; precisa de OCR/cotejo visual com exemplar oficial. | -| `Legislacao/RectificaçãoLei.13.2016_Republicação.Codigo.IVA.pdf` | 19.539.751 bytes; SHA-256 `ad675fef757016b730e859c834ab1a1602576260510912dda6f9d320393d415c`; 15 páginas | Digitalização sem camada de texto; não preencher anexos rectificados por inferência. | -| `Legislacao/alteracao CIVA 2023.pdf` | 708.707 bytes; SHA-256 `0c13f4b03dadea7f2dfe30694650078fa7b9de00ef84cb47389422d23a5271cd`; 18 páginas, texto extraível | Texto está disponível para análise, mas a reconciliação artigo/vigência e autenticação ficam pendentes. | -| `Legislacao/Legislacao/Legislacao/Lei 22 2022 IVA.pdf` | 1.989.462 bytes; SHA-256 `5bd31ff1231659ce8d89e1260a9de3b067e69e4ace128b3fe0af60e2e928e6af`; 26 páginas, texto extraível | Cópia local; comparar com a publicação oficial arquivada e resolver vigências/dispositivos antes de parametrizar. | -| `Legislacao/Lei 12.2025_IRPC.pdf` | 511.828 bytes; SHA-256 `3b67d10beaa3bf65d62a3d75fa01cc5a8181000d674e7b2ba62d06c60d449757`; 4 páginas, texto extraível | O texto contém o Suplemento 1 do BR 248 e permite ler a Lei 12/2025; revisão semântica e transição por regra ainda necessárias. | -| `Legislacao/Legislacao/2009/Lei 4_2009 Código dos Benefícios Fiscais - versão consolidada.pdf` | 253.019 bytes; SHA-256 `61e3439426f1eab4c3eec91f8d4587baa2e71249c7d60bbb17c8d4427dcd0aa3`; 13 páginas | Extracção parcial com aviso de fonte tipográfica; elegibilidade não consolidada nem aprovada por benefício. | -| `Legislacao/Legislacao/a digitalizar/ISPC_Financas.pdf` | 363.424 bytes; SHA-256 `761502b64d65bfac3f9a6e1b23071a397c1c59a961bd15b27708dc01962ea1de`; 17 páginas | Digitalização com camada de texto insuficiente; distinguir do texto integral da Lei 9/2025 e do regulamento de 2026. | +| Documento localizado | Evidência técnica | Limite actual | +| --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Legislacao/Lei 13_2016_Reimpressao Codigo do IVA.pdf` | 2.127.554 bytes; SHA-256 `b90dc7c14ece2a2e3784c62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b`; 32 páginas | Sem camada de texto detectável; precisa de OCR/cotejo visual com exemplar oficial. | +| `Legislacao/RectificaçãoLei.13.2016_Republicação.Codigo.IVA.pdf` | 19.539.751 bytes; SHA-256 `ad675fef757016b730e859c834ab1a1602576260510912dda6f9d320393d415c`; 15 páginas | Digitalização sem camada de texto; não preencher anexos rectificados por inferência. | +| `Legislacao/alteracao CIVA 2023.pdf` | 708.707 bytes; SHA-256 `0c13f4b03dadea7f2dfe30694650078fa7b9de00ef84cb47389422d23a5271cd`; 18 páginas, texto extraível | Texto está disponível para análise, mas a reconciliação artigo/vigência e autenticação ficam pendentes. | +| `Legislacao/Legislacao/Legislacao/Lei 22 2022 IVA.pdf` | 1.989.462 bytes; SHA-256 `5bd31ff1231659ce8d89e1260a9de3b067e69e4ace128b3fe0af60e2e928e6af`; 26 páginas, texto extraível | Cópia local; comparar com a publicação oficial arquivada e resolver vigências/dispositivos antes de parametrizar. | +| `Legislacao/Lei 12.2025_IRPC.pdf` | 511.828 bytes; SHA-256 `3b67d10beaa3bf65d62a3d75fa01cc5a8181000d674e7b2ba62d06c60d449757`; 4 páginas, texto extraível | O texto contém o Suplemento 1 do BR 248 e permite ler a Lei 12/2025; revisão semântica e transição por regra ainda necessárias. | +| `Legislacao/Legislacao/2009/Lei 4_2009 Código dos Benefícios Fiscais - versão consolidada.pdf` | 253.019 bytes; SHA-256 `61e3439426f1eab4c3eec91f8d4587baa2e71249c7d60bbb17c8d4427dcd0aa3`; 13 páginas | Extracção parcial com aviso de fonte tipográfica; elegibilidade não consolidada nem aprovada por benefício. | +| `Legislacao/Legislacao/a digitalizar/ISPC_Financas.pdf` | 363.424 bytes; SHA-256 `761502b64d65bfac3f9a6e1b23071a397c1c59a961bd15b27708dc01962ea1de`; 17 páginas | Digitalização com camada de texto insuficiente; distinguir do texto integral da Lei 9/2025 e do regulamento de 2026. | | `Legislacao/codigo_irps.pdf`, `Legislacao/IRPColectivas.pdf` e `Servicos Tempo/Relatorios/DECLARAÇÃO DE COMPROMISSO DE HONRA.pdf` | Códigos têm camada de texto; o PDF OCAM é legível e descreve compromisso sobre o Modelo 20 da empresa para o exercício de 2025 | O compromisso não certifica o simulador, casos fiscais, nem aprovação da OCAM para o produto. Dados pessoais do PDF não são reproduzidos nesta matriz. | -O reconhecimento OCR dos PDFs digitalizados foi tentado com ferramentas temporárias locais, sem gerar uma transcrição utilizável nesta execução. Os binários originais não foram alterados nem copiados para a pasta pública. As fontes oficiais online e os textos já arquivados continuam a ser a base para confirmação; os documentos locais servem para localização e comparação, não como prova automática de autenticidade. -8. O BM publicou um comunicado operacional de 11-09-2026 para actualizar o cadastro de operadores de microcrédito de 11-09 a 10-10-2026, com apresentação de identificação e título de inscrição e possível cancelamento do registo por incumprimento. É actualização cadastral, não confirmação de novos capitais mínimos nem substituição da análise do Decreto 50/2024/Avisos. A página BM continua a listar documentação antiga e lista de operadores com corte em 31-03-2025. -9. Pendentes: cotejo integral dos anexos CIVA digitalizados; conteúdo dos BR 186/187; INSS actualizado; interpretação dos escalões ISPC e respectivo regulamento; situação normativa do Aviso 7/GBM/2017 face ao Decreto 50/2024; resultado/lista após recadastramento microcrédito de 2026; e ligação/teste independente dos casos fiscais que ainda não têm memória de cálculo rastreável. A autorização do proprietário foi concedida; não pedir autorização genérica novamente. +O reconhecimento OCR dos PDFs digitalizados foi tentado com ferramentas temporárias locais, sem gerar uma transcrição utilizável nesta execução. Os binários originais não foram alterados nem copiados para a pasta pública. As fontes oficiais online e os textos já arquivados continuam a ser a base para confirmação; os documentos locais servem para localização e comparação, não como prova automática de autenticidade. 8. O BM publicou um comunicado operacional de 11-09-2026 para actualizar o cadastro de operadores de microcrédito de 11-09 a 10-10-2026, com apresentação de identificação e título de inscrição e possível cancelamento do registo por incumprimento. É actualização cadastral, não confirmação de novos capitais mínimos nem substituição da análise do Decreto 50/2024/Avisos. A página BM continua a listar documentação antiga e lista de operadores com corte em 31-03-2025. 9. Pendentes: cotejo integral dos anexos CIVA digitalizados; conteúdo dos BR 186/187; INSS actualizado; interpretação dos escalões ISPC e respectivo regulamento; situação normativa do Aviso 7/GBM/2017 face ao Decreto 50/2024; resultado/lista após recadastramento microcrédito de 2026; e ligação/teste independente dos casos fiscais que ainda não têm memória de cálculo rastreável. A autorização do proprietário foi concedida; não pedir autorização genérica novamente. ## Casos dourados antes de activar @@ -114,12 +112,12 @@ O comunicado do Governo de 30-09-2026 é mais específico do que a ficha INM: an ### Matriz complementar — camada regulamentar anunciada em 2026 -| Imposto | Alteração indicada | Fonte oficial acessível | Diploma/artigos oficiais identificados | Vigência | Componente afectada | Testes necessários | Estado de aprovação | -| --- | --- | --- | --- | --- | --- | --- | --- | -| IRPC | Alterações ao Regulamento do Código do IRPC, associadas pelo Governo à Lei 12/2025; o comunicado contém a forma editorial “12/2005” | Comunicado do Governo, 30-09-2026; sumário INM BR 248 para identificação da Lei 12/2025 | Regulamento e decreto alterador não identificados no comunicado; artigos não enumerados | Não indicada; conferir publicação e transitório no BR 186/187 | Procedimentos, obrigações acessórias, contabilidade e campos do percurso IRPC, conforme texto | Casos imediatamente antes/depois da vigência; procedimentos; categorias de sujeito; integrações com regras já aprovadas | Pendente: adquirir BR, conferir texto consolidado e Fiscalista | -| ISPC | Novo regulamento revoga Decreto 14/2009; anúncio enumera actividades abrangidas | Comunicado do Governo, 30-09-2026; INM BR 248 para Lei 9/2025 | Número/data do novo decreto, artigos, limites, exclusões e tabela não indicados; a referência do comunicado à Lei 9/2025 como alteração IRPC diverge do BR 248, que identifica a Lei 9/2025 como ISPC | Não indicada; não inferir 01-01-2026 nem transição | Elegibilidade, actividade, estabelecimentos, volume, opção, periodicidade, obrigações | Cada limite e actividade, inclusões/exclusões, serviços, múltiplos estabelecimentos e antes/depois da vigência | Pendente; nenhuma regra ISPC activa | -| IVA | Alteração regulamentar aos artigos 3, 7, 8, 15, 16-A, 18, 21, 23, 28, 36 e 37; comunicado relaciona-a com a Lei 10/2025 | Comunicado do Governo, 30-09-2026; Lei 10/2025 no sumário INM BR 248 | Decreto alterador e texto de cada artigo não identificados | Não indicada; verificar regras transitórias/publicação no BR | Regimes, operações digitais/não residentes, dedução, facturação e obrigações conforme texto integral | Regimes distintos ISPC/IVA; operações domésticas/transfronteiriças; documentos; crédito e reembolso; antes/depois | Pendente; não aplicar a notícia como regra | -| IRPS | Alterações ao Regulamento do Código do IRPS | Comunicado do Governo, 30-09-2026; Lei 11/2025 no sumário INM BR 248 | Decreto alterador e artigos não identificados | Não indicada | Residência, categorias de rendimento, retenção, obrigações anuais conforme texto | Retenção mensal vs apuramento anual; categorias; residência desconhecida; transição | Pendente; cálculos mantidos indeterminados | +| Imposto | Alteração indicada | Fonte oficial acessível | Diploma/artigos oficiais identificados | Vigência | Componente afectada | Testes necessários | Estado de aprovação | +| ------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| IRPC | Alterações ao Regulamento do Código do IRPC, associadas pelo Governo à Lei 12/2025; o comunicado contém a forma editorial “12/2005” | Comunicado do Governo, 30-09-2026; sumário INM BR 248 para identificação da Lei 12/2025 | Regulamento e decreto alterador não identificados no comunicado; artigos não enumerados | Não indicada; conferir publicação e transitório no BR 186/187 | Procedimentos, obrigações acessórias, contabilidade e campos do percurso IRPC, conforme texto | Casos imediatamente antes/depois da vigência; procedimentos; categorias de sujeito; integrações com regras já aprovadas | Pendente: adquirir BR, conferir texto consolidado e Fiscalista | +| ISPC | Novo regulamento revoga Decreto 14/2009; anúncio enumera actividades abrangidas | Comunicado do Governo, 30-09-2026; INM BR 248 para Lei 9/2025 | Número/data do novo decreto, artigos, limites, exclusões e tabela não indicados; a referência do comunicado à Lei 9/2025 como alteração IRPC diverge do BR 248, que identifica a Lei 9/2025 como ISPC | Não indicada; não inferir 01-01-2026 nem transição | Elegibilidade, actividade, estabelecimentos, volume, opção, periodicidade, obrigações | Cada limite e actividade, inclusões/exclusões, serviços, múltiplos estabelecimentos e antes/depois da vigência | Pendente; nenhuma regra ISPC activa | +| IVA | Alteração regulamentar aos artigos 3, 7, 8, 15, 16-A, 18, 21, 23, 28, 36 e 37; comunicado relaciona-a com a Lei 10/2025 | Comunicado do Governo, 30-09-2026; Lei 10/2025 no sumário INM BR 248 | Decreto alterador e texto de cada artigo não identificados | Não indicada; verificar regras transitórias/publicação no BR | Regimes, operações digitais/não residentes, dedução, facturação e obrigações conforme texto integral | Regimes distintos ISPC/IVA; operações domésticas/transfronteiriças; documentos; crédito e reembolso; antes/depois | Pendente; não aplicar a notícia como regra | +| IRPS | Alterações ao Regulamento do Código do IRPS | Comunicado do Governo, 30-09-2026; Lei 11/2025 no sumário INM BR 248 | Decreto alterador e artigos não identificados | Não indicada | Residência, categorias de rendimento, retenção, obrigações anuais conforme texto | Retenção mensal vs apuramento anual; categorias; residência desconhecida; transição | Pendente; cálculos mantidos indeterminados | Fontes oficiais consultadas nesta rechecagem: diff --git a/docs/investmoz/simulations/official-source-register.md b/docs/investmoz/simulations/official-source-register.md index 0dfad940..8600a2d4 100644 --- a/docs/investmoz/simulations/official-source-register.md +++ b/docs/investmoz/simulations/official-source-register.md @@ -7,18 +7,18 @@ Este registo identifica autoridades e documentos oficiais para revisão. Um catálogo, página informativa ou taxa publicada não basta para activar uma regra: cada parâmetro precisa do texto normativo aplicável, alterações consolidadas, âmbito, vigência, fonte arquivada e aprovação fiscal/jurídica registada. -| Domínio | Fonte oficial localizada | Evidência confirmada | O que falta antes de usar no motor | -| --- | --- | --- | --- | -| IRPC, IVA, IRPS, ISPC e retenções | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal) | O catálogo da AT lista legislação e alterações fiscais. A página não foi obtida integralmente nesta sessão; os resultados do catálogo identificam documentos, sem permitir verificar o texto consolidado. | Obter cada código/lei e alterações em vigor; mapear artigos por operação e sujeito; verificar início/fim de vigência, regulamentação e fontes específicas de retenção; aprovação do Fiscalista. Não reutilizar uma taxa histórica encontrada numa página de serviço. | -| Cadeia legislativa do CIVA | [Lei n.º 3/2012 — catálogo INM](https://inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-230112-pag-34-1-6-br-n%C2%BA-3-boletim-da-rep%C3%BAblica-i), [Lei n.º 13/2016 — republicação integral INM](https://www.inm.gov.mz/pt-br/content/suplemento-n%C2%BA-10-de-301216-pag-1426-259-290-br-n%C2%BA-156-boletim-da-rep%C3%BAblica-i-serie), [rectificação — BR 89/2017 INM](https://inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569), [Lei n.º 16/2020 — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-231220-pag-2414-1-20-br-n%C2%BA-246-boletim-da), [Lei n.º 22/2022 republicada — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da), [Lei n.º 3/2025 — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-290525-pag-478-1-2-br-n%C2%BA-95-boletim-da-rep%C3%BAblica), [Lei n.º 10/2025 — Parlamento](https://www.parlamento.mz/wp-content/uploads/2025/05/iva.pdf) | A Lei 3/2012 alterou o CIVA original (arts. 9, 12, 14, 15, 18, 19, 21 e 25); a republicação integral de 2016 é a base consolidada a partir da sua vigência e incorpora a história legislativa anterior, não se adiciona novamente a redacção de 2012. A Lei 5/2020 é identificada pelo MEF como alteração ao art. 9(13), mas o fac-símile oficial com o texto e a vigência transitória ainda não foi cotejado; a Lei 16/2020 também altera o n.º 13 do art. 9. A Lei 22/2022 alterou artigos expressos e a publicação inicial ficou sem efeito, sendo republicada no 2.º Suplemento. A Lei 3/2025 e a Lei 10/2025 são alterações posteriores, cada uma limitada ao seu dispositivo e vigência. | **Regra de consolidação:** usar a Lei 13/2016 como texto-base integral; cotejar diplomas anteriores para validar a cadeia legislativa e a redacção incorporada, sem reaplicar os seus dispositivos como alterações posteriores a 2016. O aviso do BR 89/2017 foi lido num exemplar local com hash registado: tratar os trechos dos arts. 6(5) e 14(2) como candidatos até confirmar autenticidade e efeito temporal; obter e autenticar os anexos I–III corrigidos antes de consolidar as listas de isenção. Depois, aplicar cada diploma posterior artigo a artigo e apenas no intervalo de vigência confirmado, resolvendo substituições e transições para manter uma única redacção efectiva por artigo e período. A republicação da Lei 22/2022 é do diploma alterador, não uma nova republicação integral do CIVA. | -| INSS — regime geral e TCP | [INSS — Legislação](https://www.inss.gov.mz/legislacao/), [direitos e obrigações do contribuinte](https://www.inss.gov.mz/direitos-e-obrigacoes-do-contribuinte/) | Resultados oficiais de pesquisa apontam para o RSSO aprovado pelo Decreto 51/2017, alterações incluindo Decreto 56/2024 e páginas administrativas que descrevem contribuição repartida entre entidade e trabalhador. O portal expirou por timeout ao abrir nesta sessão. | Descarregar e conferir os diplomas, alterações e regime TCP, base contributiva, exclusões, limites e vigência. A indicação administrativa de percentagens não está aprovada como parâmetro de produção. | -| Pauta aduaneira e direitos | [AT — Pauta Aduaneira de Serviço 2026](https://www.at.gov.mz/servicos/pauta-aduaneira), [bundle público da SPA AT](https://www.at.gov.mz/assets/index-XEcizcP2.js), [Portal e-Viajante / AT](https://e-viajante.at.gov.mz/) | A página oficial anuncia a pauta em vigor para descarga Excel/PDF e um simulador com 5.822 códigos, descrição, unidade, direitos, ICE e IVA; identifica os dados como baseados na Pauta Aduaneira de Serviço 2026. A interface é uma SPA: o bundle JavaScript público, referenciado pelo HTML oficial, tem 4.075.657 bytes e SHA-256 `ba71dd79dbad00588027c3557fceb7eb746aba8861fb5be64f4e368b95f4cd25`. Uma contagem read-only de `codigo` no bundle encontrou 5.822 valores, todos distintos. A página directa e links Excel/PDF não ficaram disponíveis para obtenção nesta sessão; não foi validado integralmente cada campo/taxa. | Obter o Excel/PDF oficial, guardar data e hash, conferir contra Lei 8/2025/anexos e validar código, unidade, origem, preferência, medidas, vigência e notas. O bundle comprova presença dos identificadores no cliente oficial, não substitui a pauta arquivada nem a validação normativa dos valores. Sem classificação não calcular DA. | -| Câmbio | [Banco de Moçambique — taxas de referência](https://www.bancomoc.mz/pt/areas-wrapper/taxas-de-cambio-de-referencia-intradiaria/) | A página define a taxa de referência cambial e horários de publicação. | Confirmar qual taxa, data e método são legalmente aplicáveis à valoração aduaneira/tributária. A taxa de referência do mercado não é automaticamente a taxa aduaneira. | -| ICE e impostos de importação | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal), [Portal e-Viajante / AT](https://e-viajante.at.gov.mz/) | Os catálogos oficiais são pontos de entrada para legislação e pautas. Não foi obtida regra completa por produto nem base de cálculo. | Recolher diploma, anexo, código de produto, unidade, base, taxa específica/ad valorem, acumulações, isenções, vigência e fonte arquivada. | -| Benefícios e incentivos | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal) e legislação sectorial a localizar | O catálogo permite procurar legislação, sem confirmar elegibilidade de um caso. | Identificar diploma e autoridade competente por benefício, regime, certificado, período e critérios; validar prova documental. Até então, apresentar apenas oportunidade a validar. | -| Reforma fiscal publicada em 2025 para períodos de 2026 | [INM — Suplemento n.º 1 do BR 248, 29-12-2025](https://inm.gov.mz/pt-br/content/sum%C3%A1rio-suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1), [AT — Pauta Aduaneira 2026](https://www.at.gov.mz/servicos/pauta-aduaneira) | INM confirma no sumário as Leis 7/2025 (ICE), 8/2025 (Pauta), 9/2025 (ISPC), 10/2025 (IVA), 11/2025 (IRPS) e 12/2025 (IRPC). A AT publica uma pauta de serviço 2026 por código. | Arquivar íntegra oficial autenticada e anexos, conferir texto consolidado e regulamentos anunciados em 30-09-2026, fazer casos dourados e obter aprovação do Fiscalista. Não reutilizar tabelas secundárias como parâmetros executáveis. | -| Regulamentos fiscais aprovados em Setembro de 2026 | [Portal do Governo — comunicado de 30-09-2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/), [catálogo oficial da INM — Boletins da República](https://inm.gov.mz/pt-br/bulletin?field_tipo_de_produto_tid_br=43), [ficha INM BR 186](https://inm.gov.mz/pt-br/content/br-n%C2%BA-186-de-300926-boletim-da-rep%C3%BAblica-i-serie), [ficha INM BR 187](https://inm.gov.mz/pt-br/content/br-n%C2%BA-187-de-011026-boletim-da-rep%C3%BAblica-i-serie) | O comunicado oficial do Governo especifica que o novo regulamento ISPC revoga o Decreto 14/2009 e enumera sectores; que o IVA altera os arts. 3, 7, 8, 15, 16-A, 18, 21, 23, 28, 36 e 37 do Regulamento do Decreto 7/2008; e que há alterações regulamentares a IRPC e IRPS. O INM confirma que os BR n.º 186 e 187 foram listados em 02-10-2026, mas as fichas públicas encaminham para a loja e não expõem o sumário/texto dos actos. O comunicado não identifica números dos decretos, texto publicado, vigência ou transição. | Obter ambos os BR e confirmar quais actos contêm, número, texto integral, artigos, publicação, vigência e transição. Corrigir referências editoriais do comunicado: Lei 12/2025 é IRPC e Lei 9/2025 é ISPC segundo o BR 248; o comunicado escreve “12/2005” e associa Lei 9/2025 ao Código IRPC. Não activar com base em notícia ou aprovação governamental. | -| Microfinanças | [BM — licenciamento e legislação do sistema](https://bancomoc.mz/pt/areas-de-actuacao/licenciamento/licenciamento-de-instituicoes/), [Decreto 50/2024 em PDF oficial](https://www.bancomoc.mz/media/jcdnvi0r/decreto-50-2024-11-de-julho-regulamento-da-lei-das-institui%C3%A7%C3%B5es-de-cr%C3%A9dito-e-sociedades-financeiras.pdf), [comunicado cadastral BM de 11-09-2026](https://bancomoc.mz/media/j2fnsbjp/drl_cadastro-operadores-pt.pdf), [sumário oficial INM do BR 134/2024](https://www.inm.gov.mz/pt-br/content/sum%C3%A1rio-br-n%C2%BA-134-de-110724-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-2827) | Decreto 50/2024 revoga o Decreto 57/2004. Arts. 157–158 distinguem autorização de instituições de crédito da inscrição de operadores de microfinanças; art. 167 atribui ao BM competência para fixar fundos/capitais mínimos e limites. O BM mantém o guia de registo de 2022 marcado “Informação em actualização”, lista o Aviso 7/GBM/2017 e a lista pública de operadores com corte em 31-03-2025. Comunicado de 11-09-2026 abriu actualização cadastral até 10-10-2026, sem por si só alterar requisitos substantivos. | Confirmar validade/aplicação actual do Aviso 7/GBM/2017 e outros avisos, categoria jurídica, limites, actividade permitida, novos registos e efeitos do recadastramento 2026. Calculadora educativa não pode sugerir autorização para emprestar/captar depósitos; não reutilizar checklist antigo. | +| Domínio | Fonte oficial localizada | Evidência confirmada | O que falta antes de usar no motor | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| IRPC, IVA, IRPS, ISPC e retenções | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal) | O catálogo da AT lista legislação e alterações fiscais. A página não foi obtida integralmente nesta sessão; os resultados do catálogo identificam documentos, sem permitir verificar o texto consolidado. | Obter cada código/lei e alterações em vigor; mapear artigos por operação e sujeito; verificar início/fim de vigência, regulamentação e fontes específicas de retenção; aprovação do Fiscalista. Não reutilizar uma taxa histórica encontrada numa página de serviço. | +| Cadeia legislativa do CIVA | [Lei n.º 3/2012 — catálogo INM](https://inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-230112-pag-34-1-6-br-n%C2%BA-3-boletim-da-rep%C3%BAblica-i), [Lei n.º 13/2016 — republicação integral INM](https://www.inm.gov.mz/pt-br/content/suplemento-n%C2%BA-10-de-301216-pag-1426-259-290-br-n%C2%BA-156-boletim-da-rep%C3%BAblica-i-serie), [rectificação — BR 89/2017 INM](https://inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-br-n%C2%BA-89-de-080617-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-569), [Lei n.º 16/2020 — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-231220-pag-2414-1-20-br-n%C2%BA-246-boletim-da), [Lei n.º 22/2022 republicada — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-2-de-281222-pag-2252-25-42-br-n%C2%BA-250-boletim-da), [Lei n.º 3/2025 — INM](https://www.inm.gov.mz/pt-br/content/assembleia-da-rep%C3%BAblica-suplemento-n%C2%BA-1-de-290525-pag-478-1-2-br-n%C2%BA-95-boletim-da-rep%C3%BAblica), [Lei n.º 10/2025 — Parlamento](https://www.parlamento.mz/wp-content/uploads/2025/05/iva.pdf) | A Lei 3/2012 alterou o CIVA original (arts. 9, 12, 14, 15, 18, 19, 21 e 25); a republicação integral de 2016 é a base consolidada a partir da sua vigência e incorpora a história legislativa anterior, não se adiciona novamente a redacção de 2012. A Lei 5/2020 é identificada pelo MEF como alteração ao art. 9(13), mas o fac-símile oficial com o texto e a vigência transitória ainda não foi cotejado; a Lei 16/2020 também altera o n.º 13 do art. 9. A Lei 22/2022 alterou artigos expressos e a publicação inicial ficou sem efeito, sendo republicada no 2.º Suplemento. A Lei 3/2025 e a Lei 10/2025 são alterações posteriores, cada uma limitada ao seu dispositivo e vigência. | **Regra de consolidação:** usar a Lei 13/2016 como texto-base integral; cotejar diplomas anteriores para validar a cadeia legislativa e a redacção incorporada, sem reaplicar os seus dispositivos como alterações posteriores a 2016. O aviso do BR 89/2017 foi lido num exemplar local com hash registado: tratar os trechos dos arts. 6(5) e 14(2) como candidatos até confirmar autenticidade e efeito temporal; obter e autenticar os anexos I–III corrigidos antes de consolidar as listas de isenção. Depois, aplicar cada diploma posterior artigo a artigo e apenas no intervalo de vigência confirmado, resolvendo substituições e transições para manter uma única redacção efectiva por artigo e período. A republicação da Lei 22/2022 é do diploma alterador, não uma nova republicação integral do CIVA. | +| INSS — regime geral e TCP | [INSS — Legislação](https://www.inss.gov.mz/legislacao/), [direitos e obrigações do contribuinte](https://www.inss.gov.mz/direitos-e-obrigacoes-do-contribuinte/) | Resultados oficiais de pesquisa apontam para o RSSO aprovado pelo Decreto 51/2017, alterações incluindo Decreto 56/2024 e páginas administrativas que descrevem contribuição repartida entre entidade e trabalhador. O portal expirou por timeout ao abrir nesta sessão. | Descarregar e conferir os diplomas, alterações e regime TCP, base contributiva, exclusões, limites e vigência. A indicação administrativa de percentagens não está aprovada como parâmetro de produção. | +| Pauta aduaneira e direitos | [AT — Pauta Aduaneira de Serviço 2026](https://www.at.gov.mz/servicos/pauta-aduaneira), [bundle público da SPA AT](https://www.at.gov.mz/assets/index-XEcizcP2.js), [Portal e-Viajante / AT](https://e-viajante.at.gov.mz/) | A página oficial anuncia a pauta em vigor para descarga Excel/PDF e um simulador com 5.822 códigos, descrição, unidade, direitos, ICE e IVA; identifica os dados como baseados na Pauta Aduaneira de Serviço 2026. A interface é uma SPA: o bundle JavaScript público, referenciado pelo HTML oficial, tem 4.075.657 bytes e SHA-256 `ba71dd79dbad00588027c3557fceb7eb746aba8861fb5be64f4e368b95f4cd25`. Uma contagem read-only de `codigo` no bundle encontrou 5.822 valores, todos distintos. A página directa e links Excel/PDF não ficaram disponíveis para obtenção nesta sessão; não foi validado integralmente cada campo/taxa. | Obter o Excel/PDF oficial, guardar data e hash, conferir contra Lei 8/2025/anexos e validar código, unidade, origem, preferência, medidas, vigência e notas. O bundle comprova presença dos identificadores no cliente oficial, não substitui a pauta arquivada nem a validação normativa dos valores. Sem classificação não calcular DA. | +| Câmbio | [Banco de Moçambique — taxas de referência](https://www.bancomoc.mz/pt/areas-wrapper/taxas-de-cambio-de-referencia-intradiaria/) | A página define a taxa de referência cambial e horários de publicação. | Confirmar qual taxa, data e método são legalmente aplicáveis à valoração aduaneira/tributária. A taxa de referência do mercado não é automaticamente a taxa aduaneira. | +| ICE e impostos de importação | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal), [Portal e-Viajante / AT](https://e-viajante.at.gov.mz/) | Os catálogos oficiais são pontos de entrada para legislação e pautas. Não foi obtida regra completa por produto nem base de cálculo. | Recolher diploma, anexo, código de produto, unidade, base, taxa específica/ad valorem, acumulações, isenções, vigência e fonte arquivada. | +| Benefícios e incentivos | [Autoridade Tributária — Legislação Fiscal](https://www.at.gov.mz/legislacao/fiscal) e legislação sectorial a localizar | O catálogo permite procurar legislação, sem confirmar elegibilidade de um caso. | Identificar diploma e autoridade competente por benefício, regime, certificado, período e critérios; validar prova documental. Até então, apresentar apenas oportunidade a validar. | +| Reforma fiscal publicada em 2025 para períodos de 2026 | [INM — Suplemento n.º 1 do BR 248, 29-12-2025](https://inm.gov.mz/pt-br/content/sum%C3%A1rio-suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1), [AT — Pauta Aduaneira 2026](https://www.at.gov.mz/servicos/pauta-aduaneira) | INM confirma no sumário as Leis 7/2025 (ICE), 8/2025 (Pauta), 9/2025 (ISPC), 10/2025 (IVA), 11/2025 (IRPS) e 12/2025 (IRPC). A AT publica uma pauta de serviço 2026 por código. | Arquivar íntegra oficial autenticada e anexos, conferir texto consolidado e regulamentos anunciados em 30-09-2026, fazer casos dourados e obter aprovação do Fiscalista. Não reutilizar tabelas secundárias como parâmetros executáveis. | +| Regulamentos fiscais aprovados em Setembro de 2026 | [Portal do Governo — comunicado de 30-09-2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/), [catálogo oficial da INM — Boletins da República](https://inm.gov.mz/pt-br/bulletin?field_tipo_de_produto_tid_br=43), [ficha INM BR 186](https://inm.gov.mz/pt-br/content/br-n%C2%BA-186-de-300926-boletim-da-rep%C3%BAblica-i-serie), [ficha INM BR 187](https://inm.gov.mz/pt-br/content/br-n%C2%BA-187-de-011026-boletim-da-rep%C3%BAblica-i-serie) | O comunicado oficial do Governo especifica que o novo regulamento ISPC revoga o Decreto 14/2009 e enumera sectores; que o IVA altera os arts. 3, 7, 8, 15, 16-A, 18, 21, 23, 28, 36 e 37 do Regulamento do Decreto 7/2008; e que há alterações regulamentares a IRPC e IRPS. O INM confirma que os BR n.º 186 e 187 foram listados em 02-10-2026, mas as fichas públicas encaminham para a loja e não expõem o sumário/texto dos actos. O comunicado não identifica números dos decretos, texto publicado, vigência ou transição. | Obter ambos os BR e confirmar quais actos contêm, número, texto integral, artigos, publicação, vigência e transição. Corrigir referências editoriais do comunicado: Lei 12/2025 é IRPC e Lei 9/2025 é ISPC segundo o BR 248; o comunicado escreve “12/2005” e associa Lei 9/2025 ao Código IRPC. Não activar com base em notícia ou aprovação governamental. | +| Microfinanças | [BM — licenciamento e legislação do sistema](https://bancomoc.mz/pt/areas-de-actuacao/licenciamento/licenciamento-de-instituicoes/), [Decreto 50/2024 em PDF oficial](https://www.bancomoc.mz/media/jcdnvi0r/decreto-50-2024-11-de-julho-regulamento-da-lei-das-institui%C3%A7%C3%B5es-de-cr%C3%A9dito-e-sociedades-financeiras.pdf), [comunicado cadastral BM de 11-09-2026](https://bancomoc.mz/media/j2fnsbjp/drl_cadastro-operadores-pt.pdf), [sumário oficial INM do BR 134/2024](https://www.inm.gov.mz/pt-br/content/sum%C3%A1rio-br-n%C2%BA-134-de-110724-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-2827) | Decreto 50/2024 revoga o Decreto 57/2004. Arts. 157–158 distinguem autorização de instituições de crédito da inscrição de operadores de microfinanças; art. 167 atribui ao BM competência para fixar fundos/capitais mínimos e limites. O BM mantém o guia de registo de 2022 marcado “Informação em actualização”, lista o Aviso 7/GBM/2017 e a lista pública de operadores com corte em 31-03-2025. Comunicado de 11-09-2026 abriu actualização cadastral até 10-10-2026, sem por si só alterar requisitos substantivos. | Confirmar validade/aplicação actual do Aviso 7/GBM/2017 e outros avisos, categoria jurídica, limites, actividade permitida, novos registos e efeitos do recadastramento 2026. Calculadora educativa não pode sugerir autorização para emprestar/captar depósitos; não reutilizar checklist antigo. | ## Regra de governação diff --git a/docs/investmoz/simulations/openapi.json b/docs/investmoz/simulations/openapi.json index ab33817b..efe340e1 100644 --- a/docs/investmoz/simulations/openapi.json +++ b/docs/investmoz/simulations/openapi.json @@ -1,126 +1,1803 @@ { - "openapi": "3.1.0", - "info": {"title": "InvestMoz Simulation Execution API", "version": "1.0.0", "description": "Indicative arithmetic scenarios. Rates and reductions are user supplied, not legal rates or eligibility decisions."}, - "servers": [{"url": "/api/v1"}], - "paths": { - "/tax-parameters/resolve": { - "post": { - "summary": "Resolve published, source-validated tax parameters without calculating tax", - "description": "Returns only parameters whose published rule and validated source cover the requested period and whose declared conditions match. Missing parameters, context mismatches, overlaps and period changes remain indeterminate. CSRF applies because this read uses the web middleware/RLS context.", - "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": false, "required": ["component", "period_start", "period_end"], "properties": {"component": {"type": "string", "enum": ["IRPC", "IVA", "ISPC", "IRPS", "INSS", "RETENCOES", "DIREITOS_ADUANEIROS", "ICE"]}, "period_start": {"type": "string", "format": "date"}, "period_end": {"type": "string", "format": "date"}, "context": {"type": "object", "additionalProperties": {"type": ["string", "null"]}}}}}}}, - "responses": {"200": {"description": "Resolved parameter records or explicit indeterminate states; no formula is evaluated", "content": {"application/json": {"schema": {"type": "object", "required": ["component", "period", "assumptions", "parameters"], "properties": {"component": {"type": "string"}, "period": {"type": "object", "properties": {"start": {"type": "string", "format": "date"}, "end": {"type": "string", "format": "date"}}}, "assumptions": {"type": "object"}, "parameters": {"type": "array", "items": {"type": "object", "required": ["parameter_code", "status", "value"], "properties": {"parameter_code": {"type": "string"}, "status": {"type": "string", "enum": ["resolved", "indeterminate_missing", "indeterminate_context", "indeterminate_scope", "indeterminate_overlap", "indeterminate_period_change", "indeterminate_invalid"]}, "value": {"type": ["string", "null"]}, "unit": {"type": "string"}, "conditions": {"type": "object"}, "scope_key": {"type": "string"}, "rule_code": {"type": "string"}, "rule_version": {"type": "integer"}, "validity": {"type": "object"}, "source": {"type": "object"}}}}}}}}}, "422": {"description": "Invalid period, component or context", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}} - } + "openapi": "3.1.0", + "info": { + "title": "InvestMoz Simulation Execution API", + "version": "1.0.0", + "description": "Indicative arithmetic scenarios. Rates and reductions are user supplied, not legal rates or eligibility decisions." }, - "/simulation-drafts": { - "post": {"summary": "Create an encrypted, versioned simulator form draft", "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}, {"$ref": "#/components/parameters/SimulationToken"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationFormDraft"}}}}, "responses": {"201": {"description": "Created with ETag", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftWriteResponseV1"}}}}, "200": {"description": "Idempotent replay", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftWriteResponseV1"}}}}, "409": {"description": "Draft exists or key reused with a different payload", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "422": {"description": "Invalid draft field", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}, - "get": {"summary": "List private projects owned by the signed-in account", "description": "Requires an authenticated session. Only summary fields are returned.", "responses": {"200": {"description": "Private draft summaries", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/SimulationDraftSummaryV1"}}}}}}}, "401": {"description": "Authentication required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/current": { - "get": {"summary": "Read the current user's or anonymous token's draft", "description": "A read does not extend the anonymous recovery period.", "parameters": [{"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Draft snapshot and ETag; personal fields are decrypted only for its owner", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftReadResponseV1"}}}}}}, - "delete": {"summary": "Forget the current anonymous draft on this device", "description": "Soft-deletes only the anonymous draft for the current recovery token and expires its recovery cookie. Account-owned projects are unaffected.", "parameters": [{"$ref": "#/components/parameters/SimulationToken"}], "responses": {"204": {"description": "Recovery forgotten and cookie cleared"}, "422": {"description": "Anonymous recovery token required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/claim-preview": { - "get": {"summary": "Preview an anonymous draft for explicit account association", "description": "Requires a signed-in account and the same browser recovery cookie; returns summary only and performs no transfer.", "responses": {"200": {"description": "Candidate draft summary or null", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"oneOf": [{"$ref": "#/components/schemas/SimulationDraftSummaryV1"}, {"type": "null"}]}}}}}}}} - }, - "/simulation-drafts/claim": { - "post": {"summary": "Explicitly continue an existing private project or claim as another project", "description": "Requires a verified account. `continue_existing` opens the selected account draft and leaves the anonymous draft untouched. `save_as_new` atomically transfers the anonymous row into the account without merging or replacing any draft; replay is idempotent.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": false, "required": ["choice"], "properties": {"choice": {"type": "string", "enum": ["continue_existing", "save_as_new"]}, "existing_draft_id": {"type": "string", "format": "uuid"}}}}}}, "responses": {"200": {"description": "Selected/claimed private dossier and ETag", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftReadResponseV1"}}}}, "403": {"description": "Verified account required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "404": {"description": "Anonymous draft expired or selected dossier is not owned", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "409": {"description": "Ownership conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/linkable-businesses": { - "get": {"summary": "List the account owner's businesses eligible for an explicit private link", "description": "Returns identifiers and labels only; no business data is copied to a project dossier.", "responses": {"200": {"description": "Owned business choices", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "required": ["id", "title", "modality", "status"], "properties": {"id": {"type": "string", "format": "uuid"}, "title": {"type": "string"}, "modality": {"type": "string"}, "status": {"type": "string"}}}}}}}}}}} - }, - "/simulation-drafts/{id}/business-link": { - "get": {"summary": "Read the active reversible link to an owned business", "parameters": [{"$ref": "#/components/parameters/Id"}], "responses": {"200": {"description": "Active business reference or null"}, "404": {"description": "Dossier not owned", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}, - "post": {"summary": "Explicitly link a private project to an owned business", "description": "Stores a reference only. Does not copy data, alter the business, submit it, or publish it. The owner may unlink later.", "parameters": [{"$ref": "#/components/parameters/Id"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": false, "required": ["business_id"], "properties": {"business_id": {"type": "string", "format": "uuid"}}}}}}, "responses": {"201": {"description": "Link created"}, "200": {"description": "Idempotent replay"}, "403": {"description": "Business not owned", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "409": {"description": "Unlink the current business before changing the association", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}, - "delete": {"summary": "Remove the active business association without deleting either record", "parameters": [{"$ref": "#/components/parameters/Id"}], "responses": {"200": {"description": "Association ended; immutable link history retained"}, "404": {"description": "Dossier not owned", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/{id}": { - "get": {"summary": "Read an owned draft", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Draft snapshot and ETag", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftReadResponseV1"}}}}, "404": {"description": "Draft inaccessible", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}, - "put": {"summary": "Autosave an owned draft with optimistic locking", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/IfMatch"}, {"$ref": "#/components/parameters/SimulationToken"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationFormDraft"}}}}, "responses": {"200": {"description": "Saved with new ETag and snapshot hash", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftWriteResponseV1"}}}}, "412": {"description": "Stale draft version", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "422": {"description": "Invalid draft field", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}, - "delete": {"summary": "Soft-delete an owned draft", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/IfMatch"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"204": {"description": "Deleted"}, "412": {"description": "Stale draft version", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/{id}/touch": { - "post": {"summary": "Renew anonymous recovery after an explicit continue-later action", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/IfMatch"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Recovery deadline renewed without changing the snapshot version"}, "412": {"description": "Stale draft version", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulation-drafts/{id}/conversions": { - "post": { - "summary": "Attach a completed simulation to an investment draft with explicit consent", - "description": "The caller must own the draft and the simulation, or hold the matching anonymous token. The server requires an investment purpose, a completed execution and a ready report whose SHA-256 and canonical snapshot match the execution result. Repeating the same simulation attachment is idempotent. This operation only adds an immutable simulation reference to the draft; it does not submit a project or create a CRM record.", - "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/IfMatch"}, {"$ref": "#/components/parameters/SimulationToken"}], - "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AttachSimulationToDraftInput"}}}}, - "responses": { - "200": {"description": "Draft with the simulation reference and the replayed flag; returns the new ETag when attached", "headers": {"ETag": {"schema": {"type": "string"}}, "Cache-Control": {"schema": {"type": "string", "const": "no-store, private"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationDraftAttachResponseV1"}}}}, - "404": {"description": "Draft or simulation is missing or outside the caller's authorized scope", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, - "409": {"description": "Simulation is incomplete, has no ready report, or its report hash/content does not match the result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, - "412": {"description": "If-Match is stale", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, - "422": {"description": "Consent is not granted or draft purpose is not an investment journey", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, - "428": {"description": "If-Match is required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, - "429": {"description": "Draft-write rate limit exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}} + "servers": [{ "url": "/api/v1" }], + "paths": { + "/tax-parameters/resolve": { + "post": { + "summary": "Resolve published, source-validated tax parameters without calculating tax", + "description": "Returns only parameters whose published rule and validated source cover the requested period and whose declared conditions match. Missing parameters, context mismatches, overlaps and period changes remain indeterminate. CSRF applies because this read uses the web middleware/RLS context.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "component", + "period_start", + "period_end" + ], + "properties": { + "component": { + "type": "string", + "enum": [ + "IRPC", + "IVA", + "ISPC", + "IRPS", + "INSS", + "RETENCOES", + "DIREITOS_ADUANEIROS", + "ICE" + ] + }, + "period_start": { + "type": "string", + "format": "date" + }, + "period_end": { + "type": "string", + "format": "date" + }, + "context": { + "type": "object", + "additionalProperties": { + "type": ["string", "null"] + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Resolved parameter records or explicit indeterminate states; no formula is evaluated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "component", + "period", + "assumptions", + "parameters" + ], + "properties": { + "component": { "type": "string" }, + "period": { + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "date" + }, + "end": { + "type": "string", + "format": "date" + } + } + }, + "assumptions": { "type": "object" }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "required": [ + "parameter_code", + "status", + "value" + ], + "properties": { + "parameter_code": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "resolved", + "indeterminate_missing", + "indeterminate_context", + "indeterminate_scope", + "indeterminate_overlap", + "indeterminate_period_change", + "indeterminate_invalid" + ] + }, + "value": { + "type": [ + "string", + "null" + ] + }, + "unit": { + "type": "string" + }, + "conditions": { + "type": "object" + }, + "scope_key": { + "type": "string" + }, + "rule_code": { + "type": "string" + }, + "rule_version": { + "type": "integer" + }, + "validity": { + "type": "object" + }, + "source": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Invalid period, component or context", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts": { + "post": { + "summary": "Create an encrypted, versioned simulator form draft", + "parameters": [ + { "$ref": "#/components/parameters/IdempotencyKey" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationFormDraft" + } + } + } + }, + "responses": { + "201": { + "description": "Created with ETag", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftWriteResponseV1" + } + } + } + }, + "200": { + "description": "Idempotent replay", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftWriteResponseV1" + } + } + } + }, + "409": { + "description": "Draft exists or key reused with a different payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "422": { + "description": "Invalid draft field", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + }, + "get": { + "summary": "List private projects owned by the signed-in account", + "description": "Requires an authenticated session. Only summary fields are returned.", + "responses": { + "200": { + "description": "Private draft summaries", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimulationDraftSummaryV1" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/current": { + "get": { + "summary": "Read the current user's or anonymous token's draft", + "description": "A read does not extend the anonymous recovery period.", + "parameters": [ + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Draft snapshot and ETag; personal fields are decrypted only for its owner", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftReadResponseV1" + } + } + } + } + } + }, + "delete": { + "summary": "Forget the current anonymous draft on this device", + "description": "Soft-deletes only the anonymous draft for the current recovery token and expires its recovery cookie. Account-owned projects are unaffected.", + "parameters": [ + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "204": { + "description": "Recovery forgotten and cookie cleared" + }, + "422": { + "description": "Anonymous recovery token required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/claim-preview": { + "get": { + "summary": "Preview an anonymous draft for explicit account association", + "description": "Requires a signed-in account and the same browser recovery cookie; returns summary only and performs no transfer.", + "responses": { + "200": { + "description": "Candidate draft summary or null", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/SimulationDraftSummaryV1" + }, + { "type": "null" } + ] + } + } + } + } + } + } + } + } + }, + "/simulation-drafts/claim": { + "post": { + "summary": "Explicitly continue an existing private project or claim as another project", + "description": "Requires a verified account. `continue_existing` opens the selected account draft and leaves the anonymous draft untouched. `save_as_new` atomically transfers the anonymous row into the account without merging or replacing any draft; replay is idempotent.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": ["choice"], + "properties": { + "choice": { + "type": "string", + "enum": [ + "continue_existing", + "save_as_new" + ] + }, + "existing_draft_id": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Selected/claimed private dossier and ETag", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftReadResponseV1" + } + } + } + }, + "403": { + "description": "Verified account required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "404": { + "description": "Anonymous draft expired or selected dossier is not owned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "409": { + "description": "Ownership conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/linkable-businesses": { + "get": { + "summary": "List the account owner's businesses eligible for an explicit private link", + "description": "Returns identifiers and labels only; no business data is copied to a project dossier.", + "responses": { + "200": { + "description": "Owned business choices", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "modality", + "status" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "modality": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "/simulation-drafts/{id}/business-link": { + "get": { + "summary": "Read the active reversible link to an owned business", + "parameters": [{ "$ref": "#/components/parameters/Id" }], + "responses": { + "200": { + "description": "Active business reference or null" + }, + "404": { + "description": "Dossier not owned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + }, + "post": { + "summary": "Explicitly link a private project to an owned business", + "description": "Stores a reference only. Does not copy data, alter the business, submit it, or publish it. The owner may unlink later.", + "parameters": [{ "$ref": "#/components/parameters/Id" }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": ["business_id"], + "properties": { + "business_id": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "201": { "description": "Link created" }, + "200": { "description": "Idempotent replay" }, + "403": { + "description": "Business not owned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "409": { + "description": "Unlink the current business before changing the association", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + }, + "delete": { + "summary": "Remove the active business association without deleting either record", + "parameters": [{ "$ref": "#/components/parameters/Id" }], + "responses": { + "200": { + "description": "Association ended; immutable link history retained" + }, + "404": { + "description": "Dossier not owned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/{id}": { + "get": { + "summary": "Read an owned draft", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Draft snapshot and ETag", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftReadResponseV1" + } + } + } + }, + "404": { + "description": "Draft inaccessible", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + }, + "put": { + "summary": "Autosave an owned draft with optimistic locking", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/IfMatch" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationFormDraft" + } + } + } + }, + "responses": { + "200": { + "description": "Saved with new ETag and snapshot hash", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftWriteResponseV1" + } + } + } + }, + "412": { + "description": "Stale draft version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "422": { + "description": "Invalid draft field", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + }, + "delete": { + "summary": "Soft-delete an owned draft", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/IfMatch" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "204": { "description": "Deleted" }, + "412": { + "description": "Stale draft version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/{id}/touch": { + "post": { + "summary": "Renew anonymous recovery after an explicit continue-later action", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/IfMatch" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Recovery deadline renewed without changing the snapshot version" + }, + "412": { + "description": "Stale draft version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulation-drafts/{id}/conversions": { + "post": { + "summary": "Attach a completed simulation to an investment draft with explicit consent", + "description": "The caller must own the draft and the simulation, or hold the matching anonymous token. The server requires an investment purpose, a completed execution and a ready report whose SHA-256 and canonical snapshot match the execution result. Repeating the same simulation attachment is idempotent. This operation only adds an immutable simulation reference to the draft; it does not submit a project or create a CRM record.", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/IfMatch" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttachSimulationToDraftInput" + } + } + } + }, + "responses": { + "200": { + "description": "Draft with the simulation reference and the replayed flag; returns the new ETag when attached", + "headers": { + "ETag": { "schema": { "type": "string" } }, + "Cache-Control": { + "schema": { + "type": "string", + "const": "no-store, private" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationDraftAttachResponseV1" + } + } + } + }, + "404": { + "description": "Draft or simulation is missing or outside the caller's authorized scope", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "409": { + "description": "Simulation is incomplete, has no ready report, or its report hash/content does not match the result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "412": { + "description": "If-Match is stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "422": { + "description": "Consent is not granted or draft purpose is not an investment journey", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "428": { + "description": "If-Match is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "429": { + "description": "Draft-write rate limit exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/questionnaires/{code}": { + "get": { + "summary": "Read the published Portuguese questionnaire version", + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Published questionnaire and stable version", + "headers": { + "ETag": { "schema": { "type": "string" } } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireReadResponseV1" + } + } + } + }, + "404": { + "description": "No published current version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations": { + "post": { + "summary": "Create a traceable simulation", + "parameters": [ + { "$ref": "#/components/parameters/IdempotencyKey" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationInput" + } + } + } + }, + "responses": { + "202": { + "description": "Execution queued", + "headers": { + "ETag": { "schema": { "type": "string" } }, + "Location": { "schema": { "type": "string" } } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationCreateResponseV1" + } + } + } + }, + "200": { + "description": "Idempotent replay", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationCreateResponseV1" + } + } + } + }, + "409": { + "description": "Key reused with a different payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "422": { + "description": "Invalid input or missing anonymous token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "428": { + "description": "Idempotency-Key required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations/history": { + "get": { + "summary": "List simulations visible to the verified account", + "description": "Returns summary metadata for the user's personal simulations and organization simulations visible through an active membership. Anonymous runs are excluded.", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Paginated authorized simulation history", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationHistoryResponseV1" + } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "403": { + "description": "Verified email required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations/{id}": { + "get": { + "summary": "Read a simulation and its snapshots", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Simulation. Completed result snapshots preserve legacy fields and add contract_version, context and result; arithmetic status is not a fiscal conclusion.", + "headers": { + "ETag": { "schema": { "type": "string" } } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationReadResponseV1" + } + } + } + }, + "404": { + "description": "Missing or outside authorized scope", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations/{id}/scenarios/{scenarioId}": { + "patch": { + "summary": "Update a scenario with optimistic concurrency", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/ScenarioId" }, + { "$ref": "#/components/parameters/IfMatch" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScenarioInput" + } + } + } + }, + "responses": { + "202": { + "description": "Updated; re-execution queued", + "headers": { + "ETag": { "schema": { "type": "string" } } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationReadResponseV1" + } + } + } + }, + "403": { + "description": "Edit not authorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "404": { + "description": "Simulation inaccessible", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "412": { + "description": "Stale ETag", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "428": { + "description": "If-Match required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + }, + "422": { + "description": "Invalid input", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations/{id}/reports/{reportId}": { + "get": { + "summary": "Read an immutable report snapshot", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/ReportId" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Report snapshot and hash", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationReportReadResponseV1" + } + } + } + }, + "404": { + "description": "Report inaccessible", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } + }, + "/simulations/{id}/jobs": { + "get": { + "summary": "Read non-sensitive execution job status", + "description": "Returns job type, retry state and a sanitized error code; worker exception details are never returned.", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { "$ref": "#/components/parameters/SimulationToken" } + ], + "responses": { + "200": { + "description": "Authorized job status list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimulationJobsReadResponseV1" + } + } + } + }, + "404": { + "description": "Simulation inaccessible", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorV1" + } + } + } + } + } + } } - } }, - "/questionnaires/{code}": {"get": {"summary": "Read the published Portuguese questionnaire version", "parameters": [{"name": "code", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "Published questionnaire and stable version", "headers": {"ETag": {"schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuestionnaireReadResponseV1"}}}}, "404": {"description": "No published current version", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}}}, - "/simulations": { - "post": { - "summary": "Create a traceable simulation", - "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}, {"$ref": "#/components/parameters/SimulationToken"}], - "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationInput"}}}}, - "responses": {"202": {"description": "Execution queued", "headers": {"ETag": {"schema": {"type": "string"}}, "Location": {"schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationCreateResponseV1"}}}}, "200": {"description": "Idempotent replay", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationCreateResponseV1"}}}}, "409": {"description": "Key reused with a different payload", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "422": {"description": "Invalid input or missing anonymous token", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "428": {"description": "Idempotency-Key required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}} - } - }, - "/simulations/history": { - "get": {"summary": "List simulations visible to the verified account", "description": "Returns summary metadata for the user's personal simulations and organization simulations visible through an active membership. Anonymous runs are excluded.", "parameters": [{"name": "page", "in": "query", "schema": {"type": "integer", "minimum": 1, "default": 1}}, {"name": "per_page", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 20}}], "responses": {"200": {"description": "Paginated authorized simulation history", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationHistoryResponseV1"}}}}, "401": {"description": "Authentication required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "403": {"description": "Verified email required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulations/{id}": { - "get": {"summary": "Read a simulation and its snapshots", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Simulation. Completed result snapshots preserve legacy fields and add contract_version, context and result; arithmetic status is not a fiscal conclusion.", "headers": {"ETag": {"schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationReadResponseV1"}}}}, "404": {"description": "Missing or outside authorized scope", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulations/{id}/scenarios/{scenarioId}": { - "patch": { - "summary": "Update a scenario with optimistic concurrency", - "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/ScenarioId"}, {"$ref": "#/components/parameters/IfMatch"}, {"$ref": "#/components/parameters/SimulationToken"}], - "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateScenarioInput"}}}}, - "responses": {"202": {"description": "Updated; re-execution queued", "headers": {"ETag": {"schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationReadResponseV1"}}}}, "403": {"description": "Edit not authorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "404": {"description": "Simulation inaccessible", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "412": {"description": "Stale ETag", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "428": {"description": "If-Match required", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}, "422": {"description": "Invalid input", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}} - } - }, - "/simulations/{id}/reports/{reportId}": { - "get": {"summary": "Read an immutable report snapshot", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/ReportId"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Report snapshot and hash", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationReportReadResponseV1"}}}}, "404": {"description": "Report inaccessible", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} - }, - "/simulations/{id}/jobs": { - "get": {"summary": "Read non-sensitive execution job status", "description": "Returns job type, retry state and a sanitized error code; worker exception details are never returned.", "parameters": [{"$ref": "#/components/parameters/Id"}, {"$ref": "#/components/parameters/SimulationToken"}], "responses": {"200": {"description": "Authorized job status list", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationJobsReadResponseV1"}}}}, "404": {"description": "Simulation inaccessible", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiErrorV1"}}}}}} + "components": { + "parameters": { + "Id": { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + }, + "ScenarioId": { + "name": "scenarioId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + }, + "ReportId": { + "name": "reportId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + }, + "IdempotencyKey": { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 16, + "maxLength": 128 + } + }, + "SimulationToken": { + "name": "X-Simulation-Token", + "in": "header", + "required": false, + "description": "Legacy/API-client fallback for anonymous access. Browsers use the HttpOnly, SameSite=Lax investmoz_simulation_recovery cookie; only an HMAC hash is stored.", + "schema": { + "type": "string", + "minLength": 32, + "maxLength": 256 + } + }, + "IfMatch": { + "name": "If-Match", + "in": "header", + "required": true, + "description": "Strong ETag; absent returns 428, stale returns 412.", + "schema": { "type": "string" } + } + }, + "schemas": { + "ApiErrorV1": { + "type": "object", + "properties": { + "message": { "type": "string" }, + "errors": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "additionalProperties": true + }, + "QuestionnaireReadResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "code", + "version_no", + "locale", + "schema_snapshot", + "status" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "code": { "type": "string" }, + "version_no": { "type": "integer", "minimum": 1 }, + "locale": { "type": "string", "const": "pt-MZ" }, + "schema_snapshot": { "type": "object" }, + "status": { + "type": "string", + "const": "published" + }, + "valid_from": { + "type": ["string", "null"], + "format": "date" + }, + "valid_to": { + "type": ["string", "null"], + "format": "date" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "SimulationHistoryResponseV1": { + "type": "object", + "required": ["data", "meta"], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "scope", + "organization_name", + "status", + "version_no", + "created_at", + "updated_at", + "has_ready_report" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "scope": { + "type": "string", + "enum": ["personal", "organization"] + }, + "organization_name": { + "type": ["string", "null"] + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "completed", + "failed", + "cancelled" + ] + }, + "version_no": { + "type": "integer", + "minimum": 1 + }, + "created_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "updated_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "has_ready_report": { "type": "boolean" } + } + } + }, + "meta": { + "type": "object", + "required": [ + "current_page", + "last_page", + "per_page", + "total" + ], + "properties": { + "current_page": { "type": "integer", "minimum": 1 }, + "last_page": { "type": "integer", "minimum": 1 }, + "per_page": { + "type": "integer", + "minimum": 1, + "maximum": 50 + }, + "total": { "type": "integer", "minimum": 0 } + } + } + }, + "additionalProperties": true + }, + "SimulationSourceV1": { + "type": "object", + "required": [ + "simulation_id", + "source_version_no", + "result_snapshot", + "report_sha256", + "consented_at" + ], + "properties": { + "simulation_id": { "type": "string", "format": "uuid" }, + "source_version_no": { "type": "integer", "minimum": 1 }, + "result_snapshot": { "type": "object" }, + "report_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "consented_at": { "type": "string", "format": "date-time" } + }, + "additionalProperties": true + }, + "SimulationDraftSummaryV1": { + "type": "object", + "required": [ + "id", + "title", + "version_no", + "updated_at", + "expires_at" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "title": { "type": "string" }, + "version_no": { "type": "integer", "minimum": 1 }, + "updated_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "expires_at": { + "type": ["string", "null"], + "format": "date-time" + } + } + }, + "SimulationDraftV1": { + "type": "object", + "required": [ + "id", + "version_no", + "snapshot_sha256", + "personal_snapshot", + "economic_snapshot", + "simulation_sources", + "updated_at", + "expires_at" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "version_no": { "type": "integer", "minimum": 1 }, + "snapshot_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "personal_snapshot": { + "oneOf": [ + { "type": "object" }, + { "type": "array", "maxItems": 0 } + ], + "description": "Anonymous drafts return an empty array; account drafts may contain allowlisted private fields." + }, + "economic_snapshot": { "type": "object" }, + "simulation_sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimulationSourceV1" + } + }, + "updated_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "expires_at": { + "type": ["string", "null"], + "format": "date-time", + "description": "Anonymous draft deadline; extended by save or recovery access. Account-owned drafts do not expire." + } + }, + "additionalProperties": true + }, + "SimulationDraftReadResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/SimulationDraftV1" + }, + { "type": "null" } + ] + } + }, + "additionalProperties": true + }, + "SimulationDraftWriteResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "object", + "required": ["id", "version_no"], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "version_no": { "type": "integer", "minimum": 1 }, + "replayed": { "type": "boolean" } + } + }, + "expires_at": { + "type": ["string", "null"], + "format": "date-time" + } + }, + "additionalProperties": true + }, + "SimulationDraftAttachResponseV1": { + "type": "object", + "required": ["data", "replayed"], + "properties": { + "data": { + "$ref": "#/components/schemas/SimulationDraftV1" + }, + "replayed": { "type": "boolean" } + }, + "additionalProperties": true + }, + "SimulationReportReadResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "simulation_id", + "version_no", + "status", + "report_snapshot", + "report_sha256" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "simulation_id": { + "type": "string", + "format": "uuid" + }, + "version_no": { "type": "integer", "minimum": 1 }, + "status": { + "type": "string", + "enum": ["queued", "ready", "failed"] + }, + "report_snapshot": { "type": ["object", "null"] }, + "report_sha256": { + "type": ["string", "null"], + "pattern": "^[a-f0-9]{64}$" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "SimulationJobsReadResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "simulation_id", + "job_type", + "status", + "attempts", + "available_at", + "started_at", + "finished_at", + "error_code" + ], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "simulation_id": { + "type": "string", + "format": "uuid" + }, + "job_type": { + "type": "string", + "enum": ["execute", "report"] + }, + "status": { + "type": "string", + "enum": [ + "pending", + "running", + "succeeded", + "failed" + ] + }, + "attempts": { + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "available_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "finished_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "error_code": { "type": ["string", "null"] } + }, + "additionalProperties": true + } + } + }, + "additionalProperties": true + }, + "DecimalInputString": { + "type": "string", + "pattern": "^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", + "description": "Non-negative exact decimal encoded as a JSON string; numeric ranges are enforced by the API." + }, + "PercentageInputString": { + "type": "string", + "pattern": "^(?:0|[1-9][0-9]?)(?:\\.[0-9]+)?$|^100(?:\\.0+)?$", + "description": "Exact decimal percentage encoded as a JSON string from 0 through 100; the API enforces the numeric range." + }, + "SimulationInput": { + "type": "object", + "additionalProperties": false, + "required": ["base", "rate", "reduction"], + "properties": { + "base": { + "allOf": [ + { + "$ref": "#/components/schemas/DecimalInputString" + } + ], + "description": "User input; no currency is inferred." + }, + "rate": { + "allOf": [ + { + "$ref": "#/components/schemas/PercentageInputString" + } + ], + "description": "User scenario rate; not an official rate." + }, + "reduction": { + "allOf": [ + { + "$ref": "#/components/schemas/PercentageInputString" + } + ], + "description": "User supplied scenario reduction; no default is applied." + }, + "organization_id": { + "type": "string", + "format": "uuid", + "description": "Optional; requires admin, manager or officer membership." + } + } + }, + "AttachSimulationToDraftInput": { + "type": "object", + "additionalProperties": false, + "required": ["simulation_id", "consent_reuse"], + "properties": { + "simulation_id": { "type": "string", "format": "uuid" }, + "consent_reuse": { + "type": "boolean", + "const": true, + "description": "Explicit consent is required; false is rejected." + } + } + }, + "SimulationFormDraft": { + "type": "object", + "additionalProperties": false, + "required": ["personal_snapshot", "economic_snapshot"], + "properties": { + "personal_snapshot": { + "type": "object", + "description": "Allowlisted personal fields, encrypted at rest; only owner/token can read." + }, + "economic_snapshot": { + "type": "object", + "required": ["purpose"], + "description": "Fiscal rates are server-selected from approved effective versions; user-entered assumptions are confined to custom_planning.", + "properties": { + "fiscal_scenario_mode": { + "type": "string", + "enum": [ + "approved_parameters", + "custom_planning", + "no_calculation" + ] + }, + "business_plan_snapshot": { + "type": "string", + "maxLength": 60000, + "description": "Versioned business financial plan JSON snapshot (v1-v16); monthly purchase, supplier installment payment, expense, payroll and per-product inventory schedules are optional." + }, + "financial_tools_snapshot": { + "type": "string", + "maxLength": 30000, + "description": "Versioned financial calculator assumptions JSON snapshot." + } + } + } + } + }, + "UpdateScenarioInput": { + "type": "object", + "additionalProperties": false, + "required": ["label", "base", "rate", "reduction"], + "properties": { + "label": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "base": { + "allOf": [ + { + "$ref": "#/components/schemas/DecimalInputString" + } + ] + }, + "rate": { + "allOf": [ + { + "$ref": "#/components/schemas/PercentageInputString" + } + ] + }, + "reduction": { + "allOf": [ + { + "$ref": "#/components/schemas/PercentageInputString" + } + ], + "description": "User supplied scenario reduction; no default is applied." + } + } + }, + "SimulationExecutionV1": { + "type": "object", + "required": ["id", "status", "version_no", "result_snapshot"], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "completed", + "failed", + "cancelled" + ] + }, + "version_no": { "type": "integer", "minimum": 1 }, + "result_snapshot": { + "type": ["object", "null"], + "description": "Immutable calculation snapshot when available; null before completion." + } + }, + "additionalProperties": true + }, + "SimulationCreateResponseV1": { + "type": "object", + "required": ["data", "replayed"], + "properties": { + "data": { + "$ref": "#/components/schemas/SimulationExecutionV1" + }, + "replayed": { "type": "boolean" }, + "access_token": { + "type": "string", + "minLength": 32, + "maxLength": 256, + "description": "Returned only for an anonymous execution." + } + }, + "additionalProperties": true + }, + "SimulationReadResponseV1": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "$ref": "#/components/schemas/SimulationExecutionV1" + } + }, + "additionalProperties": true + }, + "SimulationResult": { + "type": "object", + "required": [ + "status", + "value", + "unit", + "explanation", + "missing_inputs", + "source_ids" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "calculated", + "estimated", + "indeterminate", + "not_applicable" + ] + }, + "value": { + "type": ["string", "null"], + "pattern": "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", + "description": "Exact decimal string; null for indeterminate or not_applicable." + }, + "unit": { "type": ["string", "null"] }, + "explanation": { "type": "string", "minLength": 1 }, + "missing_inputs": { + "type": "array", + "items": { "type": "string" } + }, + "source_ids": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "SimulationResultContext": { + "type": "object", + "required": [ + "reference_date", + "currency", + "currency_fraction_digits", + "scenario_id", + "scenario_version", + "engine_version", + "ruleset_version" + ], + "properties": { + "reference_date": { "type": "string", "format": "date" }, + "currency": { "type": ["string", "null"] }, + "currency_fraction_digits": { + "type": ["integer", "null"], + "minimum": 0 + }, + "scenario_id": { "type": "string", "format": "uuid" }, + "scenario_version": { "type": "integer", "minimum": 1 }, + "engine_version": { "type": "string" }, + "ruleset_version": { "type": ["string", "null"] } + } + }, + "SimulationResultSnapshotV1": { + "type": "object", + "required": [ + "contract_version", + "context", + "result", + "indicative_total", + "legal_interpretation", + "formula_version" + ], + "properties": { + "contract_version": { "type": "integer", "const": 1 }, + "context": { + "$ref": "#/components/schemas/SimulationResultContext" + }, + "result": { + "$ref": "#/components/schemas/SimulationResult" + }, + "indicative_total": { + "type": ["string", "null"], + "pattern": "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", + "description": "Legacy decimal when the result has a value; null for indeterminate or not-applicable states." + }, + "legal_interpretation": { + "type": "boolean", + "const": false + }, + "formula_version": { "type": "string" } + } + } + } } - }, - "components": { - "parameters": { - "Id": {"name": "id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, - "ScenarioId": {"name": "scenarioId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, - "ReportId": {"name": "reportId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, - "IdempotencyKey": {"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "minLength": 16, "maxLength": 128}}, - "SimulationToken": {"name": "X-Simulation-Token", "in": "header", "required": false, "description": "Legacy/API-client fallback for anonymous access. Browsers use the HttpOnly, SameSite=Lax investmoz_simulation_recovery cookie; only an HMAC hash is stored.", "schema": {"type": "string", "minLength": 32, "maxLength": 256}}, - "IfMatch": {"name": "If-Match", "in": "header", "required": true, "description": "Strong ETag; absent returns 428, stale returns 412.", "schema": {"type": "string"}} - }, - "schemas": { - "ApiErrorV1": {"type": "object", "properties": {"message": {"type": "string"}, "errors": {"type": "object", "additionalProperties": {"type": "array", "items": {"type": "string"}}}}, "additionalProperties": true}, - "QuestionnaireReadResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["id", "code", "version_no", "locale", "schema_snapshot", "status"], "properties": {"id": {"type": "string", "format": "uuid"}, "code": {"type": "string"}, "version_no": {"type": "integer", "minimum": 1}, "locale": {"type": "string", "const": "pt-MZ"}, "schema_snapshot": {"type": "object"}, "status": {"type": "string", "const": "published"}, "valid_from": {"type": ["string", "null"], "format": "date"}, "valid_to": {"type": ["string", "null"], "format": "date"}}, "additionalProperties": true}}, "additionalProperties": true}, - "SimulationHistoryResponseV1": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"type": "object", "required": ["id", "scope", "organization_name", "status", "version_no", "created_at", "updated_at", "has_ready_report"], "properties": {"id": {"type": "string", "format": "uuid"}, "scope": {"type": "string", "enum": ["personal", "organization"]}, "organization_name": {"type": ["string", "null"]}, "status": {"type": "string", "enum": ["queued", "running", "completed", "failed", "cancelled"]}, "version_no": {"type": "integer", "minimum": 1}, "created_at": {"type": ["string", "null"], "format": "date-time"}, "updated_at": {"type": ["string", "null"], "format": "date-time"}, "has_ready_report": {"type": "boolean"}}}}, "meta": {"type": "object", "required": ["current_page", "last_page", "per_page", "total"], "properties": {"current_page": {"type": "integer", "minimum": 1}, "last_page": {"type": "integer", "minimum": 1}, "per_page": {"type": "integer", "minimum": 1, "maximum": 50}, "total": {"type": "integer", "minimum": 0}}}}, "additionalProperties": true}, - "SimulationSourceV1": {"type": "object", "required": ["simulation_id", "source_version_no", "result_snapshot", "report_sha256", "consented_at"], "properties": {"simulation_id": {"type": "string", "format": "uuid"}, "source_version_no": {"type": "integer", "minimum": 1}, "result_snapshot": {"type": "object"}, "report_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, "consented_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}, - "SimulationDraftSummaryV1": {"type": "object", "required": ["id", "title", "version_no", "updated_at", "expires_at"], "properties": {"id": {"type": "string", "format": "uuid"}, "title": {"type": "string"}, "version_no": {"type": "integer", "minimum": 1}, "updated_at": {"type": ["string", "null"], "format": "date-time"}, "expires_at": {"type": ["string", "null"], "format": "date-time"}}}, - "SimulationDraftV1": {"type": "object", "required": ["id", "version_no", "snapshot_sha256", "personal_snapshot", "economic_snapshot", "simulation_sources", "updated_at", "expires_at"], "properties": {"id": {"type": "string", "format": "uuid"}, "version_no": {"type": "integer", "minimum": 1}, "snapshot_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, "personal_snapshot": {"oneOf": [{"type": "object"}, {"type": "array", "maxItems": 0}], "description": "Anonymous drafts return an empty array; account drafts may contain allowlisted private fields."}, "economic_snapshot": {"type": "object"}, "simulation_sources": {"type": "array", "items": {"$ref": "#/components/schemas/SimulationSourceV1"}}, "updated_at": {"type": ["string", "null"], "format": "date-time"}, "expires_at": {"type": ["string", "null"], "format": "date-time", "description": "Anonymous draft deadline; extended by save or recovery access. Account-owned drafts do not expire."}}, "additionalProperties": true}, - "SimulationDraftReadResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"oneOf": [{"$ref": "#/components/schemas/SimulationDraftV1"}, {"type": "null"}]}}, "additionalProperties": true}, - "SimulationDraftWriteResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["id", "version_no"], "properties": {"id": {"type": "string", "format": "uuid"}, "version_no": {"type": "integer", "minimum": 1}, "replayed": {"type": "boolean"}}}, "expires_at": {"type": ["string", "null"], "format": "date-time"}}, "additionalProperties": true}, - "SimulationDraftAttachResponseV1": {"type": "object", "required": ["data", "replayed"], "properties": {"data": {"$ref": "#/components/schemas/SimulationDraftV1"}, "replayed": {"type": "boolean"}}, "additionalProperties": true}, - "SimulationReportReadResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["id", "simulation_id", "version_no", "status", "report_snapshot", "report_sha256"], "properties": {"id": {"type": "string", "format": "uuid"}, "simulation_id": {"type": "string", "format": "uuid"}, "version_no": {"type": "integer", "minimum": 1}, "status": {"type": "string", "enum": ["queued", "ready", "failed"]}, "report_snapshot": {"type": ["object", "null"]}, "report_sha256": {"type": ["string", "null"], "pattern": "^[a-f0-9]{64}$"}}, "additionalProperties": true}}, "additionalProperties": true}, - "SimulationJobsReadResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"type": "array", "items": {"type": "object", "required": ["id", "simulation_id", "job_type", "status", "attempts", "available_at", "started_at", "finished_at", "error_code"], "properties": {"id": {"type": "string", "format": "uuid"}, "simulation_id": {"type": "string", "format": "uuid"}, "job_type": {"type": "string", "enum": ["execute", "report"]}, "status": {"type": "string", "enum": ["pending", "running", "succeeded", "failed"]}, "attempts": {"type": "integer", "minimum": 0, "maximum": 3}, "available_at": {"type": "string", "format": "date-time"}, "started_at": {"type": ["string", "null"], "format": "date-time"}, "finished_at": {"type": ["string", "null"], "format": "date-time"}, "error_code": {"type": ["string", "null"]}}, "additionalProperties": true}}}, "additionalProperties": true}, - "DecimalInputString": {"type": "string", "pattern": "^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", "description": "Non-negative exact decimal encoded as a JSON string; numeric ranges are enforced by the API."}, - "PercentageInputString": {"type": "string", "pattern": "^(?:0|[1-9][0-9]?)(?:\\.[0-9]+)?$|^100(?:\\.0+)?$", "description": "Exact decimal percentage encoded as a JSON string from 0 through 100; the API enforces the numeric range."}, - "SimulationInput": {"type": "object", "additionalProperties": false, "required": ["base", "rate", "reduction"], "properties": {"base": {"allOf": [{"$ref": "#/components/schemas/DecimalInputString"}], "description": "User input; no currency is inferred."}, "rate": {"allOf": [{"$ref": "#/components/schemas/PercentageInputString"}], "description": "User scenario rate; not an official rate."}, "reduction": {"allOf": [{"$ref": "#/components/schemas/PercentageInputString"}], "description": "User supplied scenario reduction; no default is applied."}, "organization_id": {"type": "string", "format": "uuid", "description": "Optional; requires admin, manager or officer membership."}}}, - "AttachSimulationToDraftInput": {"type": "object", "additionalProperties": false, "required": ["simulation_id", "consent_reuse"], "properties": {"simulation_id": {"type": "string", "format": "uuid"}, "consent_reuse": {"type": "boolean", "const": true, "description": "Explicit consent is required; false is rejected."}}}, - "SimulationFormDraft": {"type": "object", "additionalProperties": false, "required": ["personal_snapshot", "economic_snapshot"], "properties": {"personal_snapshot": {"type": "object", "description": "Allowlisted personal fields, encrypted at rest; only owner/token can read."}, "economic_snapshot": {"type": "object", "required": ["purpose"], "description": "Fiscal rates are server-selected from approved effective versions; user-entered assumptions are confined to custom_planning.", "properties": {"fiscal_scenario_mode": {"type": "string", "enum": ["approved_parameters", "custom_planning", "no_calculation"]}, "business_plan_snapshot": {"type": "string", "maxLength": 60000, "description": "Versioned business financial plan JSON snapshot (v1-v16); monthly purchase, supplier installment payment, expense, payroll and per-product inventory schedules are optional."}, "financial_tools_snapshot": {"type": "string", "maxLength": 30000, "description": "Versioned financial calculator assumptions JSON snapshot."}}}}}, - "UpdateScenarioInput": {"type": "object", "additionalProperties": false, "required": ["label", "base", "rate", "reduction"], "properties": {"label": {"type": "string", "minLength": 1, "maxLength": 100}, "base": {"allOf": [{"$ref": "#/components/schemas/DecimalInputString"}]}, "rate": {"allOf": [{"$ref": "#/components/schemas/PercentageInputString"}]}, "reduction": {"allOf": [{"$ref": "#/components/schemas/PercentageInputString"}], "description": "User supplied scenario reduction; no default is applied."}}}, - "SimulationExecutionV1": {"type": "object", "required": ["id", "status", "version_no", "result_snapshot"], "properties": {"id": {"type": "string", "format": "uuid"}, "status": {"type": "string", "enum": ["queued", "running", "completed", "failed", "cancelled"]}, "version_no": {"type": "integer", "minimum": 1}, "result_snapshot": {"type": ["object", "null"], "description": "Immutable calculation snapshot when available; null before completion."}}, "additionalProperties": true}, - "SimulationCreateResponseV1": {"type": "object", "required": ["data", "replayed"], "properties": {"data": {"$ref": "#/components/schemas/SimulationExecutionV1"}, "replayed": {"type": "boolean"}, "access_token": {"type": "string", "minLength": 32, "maxLength": 256, "description": "Returned only for an anonymous execution."}}, "additionalProperties": true}, - "SimulationReadResponseV1": {"type": "object", "required": ["data"], "properties": {"data": {"$ref": "#/components/schemas/SimulationExecutionV1"}}, "additionalProperties": true}, - "SimulationResult": {"type": "object", "required": ["status", "value", "unit", "explanation", "missing_inputs", "source_ids"], "properties": {"status": {"type": "string", "enum": ["calculated", "estimated", "indeterminate", "not_applicable"]}, "value": {"type": ["string", "null"], "pattern": "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", "description": "Exact decimal string; null for indeterminate or not_applicable."}, "unit": {"type": ["string", "null"]}, "explanation": {"type": "string", "minLength": 1}, "missing_inputs": {"type": "array", "items": {"type": "string"}}, "source_ids": {"type": "array", "items": {"type": "string"}}}}, - "SimulationResultContext": {"type": "object", "required": ["reference_date", "currency", "currency_fraction_digits", "scenario_id", "scenario_version", "engine_version", "ruleset_version"], "properties": {"reference_date": {"type": "string", "format": "date"}, "currency": {"type": ["string", "null"]}, "currency_fraction_digits": {"type": ["integer", "null"], "minimum": 0}, "scenario_id": {"type": "string", "format": "uuid"}, "scenario_version": {"type": "integer", "minimum": 1}, "engine_version": {"type": "string"}, "ruleset_version": {"type": ["string", "null"]}}}, - "SimulationResultSnapshotV1": {"type": "object", "required": ["contract_version", "context", "result", "indicative_total", "legal_interpretation", "formula_version"], "properties": {"contract_version": {"type": "integer", "const": 1}, "context": {"$ref": "#/components/schemas/SimulationResultContext"}, "result": {"$ref": "#/components/schemas/SimulationResult"}, "indicative_total": {"type": ["string", "null"], "pattern": "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$", "description": "Legacy decimal when the result has a value; null for indeterminate or not-applicable states."}, "legal_interpretation": {"type": "boolean", "const": false}, "formula_version": {"type": "string"}}} - } - } } diff --git a/docs/investmoz/simulations/primary-text-review-2025-2026.md b/docs/investmoz/simulations/primary-text-review-2025-2026.md index 9a9e8838..f9be851f 100644 --- a/docs/investmoz/simulations/primary-text-review-2025-2026.md +++ b/docs/investmoz/simulations/primary-text-review-2025-2026.md @@ -16,19 +16,19 @@ O URL tentado anteriormente em /index.php/por/content/download/93497/... devolvi ## Matriz do texto legal lido -| Imposto/tema | Diploma e artigos no BR 248 | Alteração legível no texto primário | Vigência escrita | Componente e testes necessários | Estado | -|---|---|---|---|---|---| -| ICE | Lei 7/2025, arts. 1–5 e tabela | Prorroga a tabela de taxas da Lei 19/2022 para 2027; altera artigos e códigos da tabela. Regulamentação pelo Conselho de Ministros prevista em 90 dias. | 01-01-2026 | Produto/código, unidade, taxa específica/ad valorem, isenção e fronteiras temporais. | Texto disponível; tabela integral, regulamentação consolidada e aprovação pendentes. | -| Aduaneiro | Lei 8/2025, arts. 1–4 e anexo | Altera franquia de viajantes (art. 21), mercadorias que podem beneficiar de redução/isenção (art. 22), calendário ZCLCA (art. 27) e códigos pautais; instruções complementares previstas em 60 dias. | 01-01-2026 | Código obrigatório, origem/preferência, calendário, unidade, base, notas e antes/depois. | Texto disponível; instruções e pauta aplicável por código não reconciliadas/aprovadas. | -| ISPC: elegibilidade | Lei 9/2025, arts. 2–6 e republicação da Lei 5/2009 | Actividades enumeradas incluem silvícola, pesqueira, pecuária, agrícola/avícola/apícola, industrial, comercial, artesanato e serviços; volume anual até MZN 4.000.000; regime optativo. Exclusões incluem participações especificadas, outras actividades, actividade distribuída por vários estabelecimentos, certos serviços à mesma entidade por mais de 183 dias e serviços fora da lista. | Aplicável a rendimentos de 2026; regulamento previsto em 180 dias. | Actividade, categoria, volume/período, estabelecimentos, participação, cliente/duração, opção e transição. | A distinção ISPC vs regimes simplificados de IVA/IRPS está confirmada. Interpretação, regulamento e aprovação pendentes. | -| ISPC: taxas/obrigações | Lei 9/2025, arts. 8–12, 9-A e 10-A | 3/4/5% por escalões de volume para actividades enumeradas; 12% para serviços exemplificados; 15% para profissões liberais; 20% sobre parte acima do limite. Cúmulo usa a taxa mais alta. Base trimestral; declaração trimestral; pagamento inferior a MZN 500 pode não ser devido, sem dispensar declaração. Retenção de 5% em certas aquisições dentro do limite global indicado. Permanência liberal até cinco anos. | 01-01-2026; transição/regulamento por analisar. | Fronteiras, actividades combinadas, excedente, trimestre, mínimo, retenção/limite e histórico de cinco anos. | Não implementar fórmula antes da aprovação da interpretação, classificação e regulamento. | -| IVA: digital/não residente | Lei 10/2025, arts. 3, 6, 23, 26, 32–33 | Inclui bens digitais e define serviços digitais; prevê operações digitais transfronteiriças e obrigações do adquirente em certos casos de não residente. | 01-01-2026; regulamento previsto em 180 dias. | Tipo/local da operação, residência/estabelecimento, adquirente e declaração. | Texto disponível; fluxo aguarda regulamentos e consolidação. | -| IVA: taxa geral | Lei 22/2022, republicada no 2.º Suplemento do BR 250, art. 17; Lei 10/2025, arts. 1 e 34 | O art. 17 da republicação fixa 16%. A Lei 10/2025 não altera o art. 17; o divisor 116 do art. 34 é coerente com 16% incluído no preço. A primeira publicação foi declarada sem efeito pela INM e a lei republicada no 2.º suplemento. | Lei 22/2022 em vigor desde 01-01-2023; art. 17 não alterado pela Lei 10/2025, efectiva em 01-01-2026. | Taxa geral, inclusão no preço, arredondamento para unidade no art. 34 e transição imediatamente antes/depois de cada alteração. | Divergência legal resolvida em favor do texto republicado; indicação de 17% nas páginas/formulário AT é desactualizada. Activação ainda requer validação e aprovação formal do Fiscalista. | -| IVA: dedução/crédito/reembolso | Lei 10/2025, arts. 18, 19, 21, 23, 25-A, 27, 32–33 | Dedução depende de factura/documento de sujeito registado ou documento legal de importação; prevê proporcionalidade e exclusões. Crédito mensal acima de MZN 500.000 pode fundamentar pedido; considera créditos antigos. Reembolso, quando devido, em 150 dias; suspensão de crédito por 60 dias nas condições descritas; pedido prescreve em dez anos. Obrigações por estabelecimento/facturas dependem de regulamentação. | 01-01-2026; processo regulamentar por confirmar. | Distinguir imposto apurado, crédito estimado, pedido, reconhecido e recebido; testar documentos, rateio, prazo, suspensão e estabelecimentos. | Crédito não equivale a dinheiro nem a reembolso aprovado. Sem previsão de caixa automática. | -| IRPS: residência/rendimentos | Lei 11/2025, arts. 21, 22, 48, 50 e correlatos | Factores de residência não se reduzem a nacionalidade ou dias; texto inclui residência principal, actividade profissional, centro de interesses e situações familiares/especiais. Inclui rendimentos digitais e despesas prediais documentadas com limite indicado. | 01-01-2026; regulamento previsto em 180 dias. | Factores autodeclarados, categoria, documentação, rendimentos digitais e cenário indeterminado. | Não inferir residência por nacionalidade/dias. Regulamentos, tratados e aplicação factual pendentes. | -| IRPS: trabalho/mais-valias | Lei 11/2025, arts. 54-A, 57, 65, 65-A | Retenção laboral mensal depende de salário bruto e dependentes; tabela autónoma de mais-valias usa escalões de 10%, 15%, 20%, 25%, 32%, não uma taxa plana. | 01-01-2026; retenção carece de regulamentação. | Limites, parcela marginal, dependentes, remuneração variável e retenção mensal vs apuramento anual. | Distinção confirmada; cálculo depende de regra consolidada e aprovação. | -| IRPC: estabelecimento/digital | Lei 12/2025, arts. 3 e 5 | Estaleiro/construção/instalação/montagem acima de 90 dias; serviços, incluindo consultoria, agregados acima de 90 dias em qualquer período de 12 meses, com exclusão expressa de serviços digitais; prevê rendimentos digitais e ganhos em transmissão directa/indirecta de participações com activos locais. | 01-01-2026; regulamento previsto em 180 dias. | Tipo, período móvel, localização, digital vs serviço, activos/participação e tratados. | Texto identificado; aplicação e interacção com tratados/regulamentos pendentes. | -| IRPC: retenção/mais-valias/contabilidade | Lei 12/2025, arts. 20, 61-A, 62, 67, 75 e transitório | Taxa autónoma de 32% para mais-valias; 10% para comissões de agentes de moeda electrónica e rendimentos digitais indicados; retenção no evento legal aplicável; transição para contabilidade organizada e informatizada nos termos regulamentares. | 01-01-2026; procedimentos pendentes. | Natureza/residência, evento de custo/pagamento/vencimento, ganho, transição e entidade. | Não transformar percentagens em aconselhamento nem aplicar sem alcance e regulamentos aprovados. | +| Imposto/tema | Diploma e artigos no BR 248 | Alteração legível no texto primário | Vigência escrita | Componente e testes necessários | Estado | +| ---------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ICE | Lei 7/2025, arts. 1–5 e tabela | Prorroga a tabela de taxas da Lei 19/2022 para 2027; altera artigos e códigos da tabela. Regulamentação pelo Conselho de Ministros prevista em 90 dias. | 01-01-2026 | Produto/código, unidade, taxa específica/ad valorem, isenção e fronteiras temporais. | Texto disponível; tabela integral, regulamentação consolidada e aprovação pendentes. | +| Aduaneiro | Lei 8/2025, arts. 1–4 e anexo | Altera franquia de viajantes (art. 21), mercadorias que podem beneficiar de redução/isenção (art. 22), calendário ZCLCA (art. 27) e códigos pautais; instruções complementares previstas em 60 dias. | 01-01-2026 | Código obrigatório, origem/preferência, calendário, unidade, base, notas e antes/depois. | Texto disponível; instruções e pauta aplicável por código não reconciliadas/aprovadas. | +| ISPC: elegibilidade | Lei 9/2025, arts. 2–6 e republicação da Lei 5/2009 | Actividades enumeradas incluem silvícola, pesqueira, pecuária, agrícola/avícola/apícola, industrial, comercial, artesanato e serviços; volume anual até MZN 4.000.000; regime optativo. Exclusões incluem participações especificadas, outras actividades, actividade distribuída por vários estabelecimentos, certos serviços à mesma entidade por mais de 183 dias e serviços fora da lista. | Aplicável a rendimentos de 2026; regulamento previsto em 180 dias. | Actividade, categoria, volume/período, estabelecimentos, participação, cliente/duração, opção e transição. | A distinção ISPC vs regimes simplificados de IVA/IRPS está confirmada. Interpretação, regulamento e aprovação pendentes. | +| ISPC: taxas/obrigações | Lei 9/2025, arts. 8–12, 9-A e 10-A | 3/4/5% por escalões de volume para actividades enumeradas; 12% para serviços exemplificados; 15% para profissões liberais; 20% sobre parte acima do limite. Cúmulo usa a taxa mais alta. Base trimestral; declaração trimestral; pagamento inferior a MZN 500 pode não ser devido, sem dispensar declaração. Retenção de 5% em certas aquisições dentro do limite global indicado. Permanência liberal até cinco anos. | 01-01-2026; transição/regulamento por analisar. | Fronteiras, actividades combinadas, excedente, trimestre, mínimo, retenção/limite e histórico de cinco anos. | Não implementar fórmula antes da aprovação da interpretação, classificação e regulamento. | +| IVA: digital/não residente | Lei 10/2025, arts. 3, 6, 23, 26, 32–33 | Inclui bens digitais e define serviços digitais; prevê operações digitais transfronteiriças e obrigações do adquirente em certos casos de não residente. | 01-01-2026; regulamento previsto em 180 dias. | Tipo/local da operação, residência/estabelecimento, adquirente e declaração. | Texto disponível; fluxo aguarda regulamentos e consolidação. | +| IVA: taxa geral | Lei 22/2022, republicada no 2.º Suplemento do BR 250, art. 17; Lei 10/2025, arts. 1 e 34 | O art. 17 da republicação fixa 16%. A Lei 10/2025 não altera o art. 17; o divisor 116 do art. 34 é coerente com 16% incluído no preço. A primeira publicação foi declarada sem efeito pela INM e a lei republicada no 2.º suplemento. | Lei 22/2022 em vigor desde 01-01-2023; art. 17 não alterado pela Lei 10/2025, efectiva em 01-01-2026. | Taxa geral, inclusão no preço, arredondamento para unidade no art. 34 e transição imediatamente antes/depois de cada alteração. | Divergência legal resolvida em favor do texto republicado; indicação de 17% nas páginas/formulário AT é desactualizada. Activação ainda requer validação e aprovação formal do Fiscalista. | +| IVA: dedução/crédito/reembolso | Lei 10/2025, arts. 18, 19, 21, 23, 25-A, 27, 32–33 | Dedução depende de factura/documento de sujeito registado ou documento legal de importação; prevê proporcionalidade e exclusões. Crédito mensal acima de MZN 500.000 pode fundamentar pedido; considera créditos antigos. Reembolso, quando devido, em 150 dias; suspensão de crédito por 60 dias nas condições descritas; pedido prescreve em dez anos. Obrigações por estabelecimento/facturas dependem de regulamentação. | 01-01-2026; processo regulamentar por confirmar. | Distinguir imposto apurado, crédito estimado, pedido, reconhecido e recebido; testar documentos, rateio, prazo, suspensão e estabelecimentos. | Crédito não equivale a dinheiro nem a reembolso aprovado. Sem previsão de caixa automática. | +| IRPS: residência/rendimentos | Lei 11/2025, arts. 21, 22, 48, 50 e correlatos | Factores de residência não se reduzem a nacionalidade ou dias; texto inclui residência principal, actividade profissional, centro de interesses e situações familiares/especiais. Inclui rendimentos digitais e despesas prediais documentadas com limite indicado. | 01-01-2026; regulamento previsto em 180 dias. | Factores autodeclarados, categoria, documentação, rendimentos digitais e cenário indeterminado. | Não inferir residência por nacionalidade/dias. Regulamentos, tratados e aplicação factual pendentes. | +| IRPS: trabalho/mais-valias | Lei 11/2025, arts. 54-A, 57, 65, 65-A | Retenção laboral mensal depende de salário bruto e dependentes; tabela autónoma de mais-valias usa escalões de 10%, 15%, 20%, 25%, 32%, não uma taxa plana. | 01-01-2026; retenção carece de regulamentação. | Limites, parcela marginal, dependentes, remuneração variável e retenção mensal vs apuramento anual. | Distinção confirmada; cálculo depende de regra consolidada e aprovação. | +| IRPC: estabelecimento/digital | Lei 12/2025, arts. 3 e 5 | Estaleiro/construção/instalação/montagem acima de 90 dias; serviços, incluindo consultoria, agregados acima de 90 dias em qualquer período de 12 meses, com exclusão expressa de serviços digitais; prevê rendimentos digitais e ganhos em transmissão directa/indirecta de participações com activos locais. | 01-01-2026; regulamento previsto em 180 dias. | Tipo, período móvel, localização, digital vs serviço, activos/participação e tratados. | Texto identificado; aplicação e interacção com tratados/regulamentos pendentes. | +| IRPC: retenção/mais-valias/contabilidade | Lei 12/2025, arts. 20, 61-A, 62, 67, 75 e transitório | Taxa autónoma de 32% para mais-valias; 10% para comissões de agentes de moeda electrónica e rendimentos digitais indicados; retenção no evento legal aplicável; transição para contabilidade organizada e informatizada nos termos regulamentares. | 01-01-2026; procedimentos pendentes. | Natureza/residência, evento de custo/pagamento/vencimento, ganho, transição e entidade. | Não transformar percentagens em aconselhamento nem aplicar sem alcance e regulamentos aprovados. | ## Base integral e sequência de alterações do CIVA diff --git a/docs/investmoz/simulations/project-dossier-print-qa-2026-10-04.json b/docs/investmoz/simulations/project-dossier-print-qa-2026-10-04.json index f7cdea8e..652cef9b 100644 --- a/docs/investmoz/simulations/project-dossier-print-qa-2026-10-04.json +++ b/docs/investmoz/simulations/project-dossier-print-qa-2026-10-04.json @@ -1,24 +1,24 @@ { - "date": "2026-10-04", - "browser": "Chromium headless via Playwright (temporary QA tools)", - "route": "Generated print HTML in memory; no portal API or database calls", - "fixture": { - "project_title": "Relatório de teste — acção e localização", - "finance_rows": 90, - "data": "Synthetic; no personal or real project data" - }, - "print": { - "format": "A4", - "pages": 8, - "pdf_bytes": 70459, - "html_declares_a4": true, - "horizontal_overflow": false, - "title": "Relatório — Relatório de teste — acção e localização", - "pdf_persisted": false - }, - "limits": [ - "This checks Chromium print pagination for the generated HTML only.", - "It does not render the DOCX in Microsoft Word or LibreOffice.", - "Page count can vary with browser fonts and print settings." - ] + "date": "2026-10-04", + "browser": "Chromium headless via Playwright (temporary QA tools)", + "route": "Generated print HTML in memory; no portal API or database calls", + "fixture": { + "project_title": "Relatório de teste — acção e localização", + "finance_rows": 90, + "data": "Synthetic; no personal or real project data" + }, + "print": { + "format": "A4", + "pages": 8, + "pdf_bytes": 70459, + "html_declares_a4": true, + "horizontal_overflow": false, + "title": "Relatório — Relatório de teste — acção e localização", + "pdf_persisted": false + }, + "limits": [ + "This checks Chromium print pagination for the generated HTML only.", + "It does not render the DOCX in Microsoft Word or LibreOffice.", + "Page count can vary with browser fonts and print settings." + ] } diff --git a/docs/investmoz/simulations/rule-governance-openapi.json b/docs/investmoz/simulations/rule-governance-openapi.json index 7c30c5b5..95b608d8 100644 --- a/docs/investmoz/simulations/rule-governance-openapi.json +++ b/docs/investmoz/simulations/rule-governance-openapi.json @@ -1,52 +1,289 @@ { - "openapi": "3.1.0", - "info": { - "title": "InvestMoz Tax Rule Governance API", - "version": "0.1.0", - "description": "Management-only rule version workflow. Records are not read by the simulation engine; no fiscal parameters are activated by this API." - }, - "servers": [{"url": "/api/v2/investment/admin"}], - "security": [{"webSession": []}], - "paths": { - "/tax-rules": { - "get": { - "summary": "List rule versions for designated governance users", - "parameters": [ - {"name": "rule_code", "in": "query", "schema": {"type": "string", "maxLength": 64}}, - {"name": "component", "in": "query", "schema": {"$ref": "#/components/schemas/Component"}}, - {"name": "scope_key", "in": "query", "schema": {"type": "string", "maxLength": 160}}, - {"name": "status", "in": "query", "schema": {"$ref": "#/components/schemas/Status"}}, - {"name": "per_page", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100}} - ], - "responses": {"200": {"description": "Paginated rule versions"}, "403": {"description": "Role or MFA required"}} - }, - "post": { - "summary": "Create a draft version (editor role)", - "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleVersionInput"}}}}, - "responses": {"201": {"description": "Draft saved with version_no 1"}, "403": {"description": "Editor role and MFA required"}, "422": {"description": "Invalid source, fields, or scope"}} - } + "openapi": "3.1.0", + "info": { + "title": "InvestMoz Tax Rule Governance API", + "version": "0.1.0", + "description": "Management-only rule version workflow. Records are not read by the simulation engine; no fiscal parameters are activated by this API." }, - "/tax-rules/{id}": { - "get": {"summary": "Read a rule version and its governance state", "parameters": [{"$ref": "#/components/parameters/Id"}], "responses": {"200": {"description": "Private governance record"}, "404": {"description": "Not found"}}}, - "put": {"summary": "Edit the author's draft with optimistic locking", "parameters": [{"$ref": "#/components/parameters/Id"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/RuleVersionInput"}, {"type": "object", "required": ["version_no"], "properties": {"version_no": {"type": "integer", "minimum": 1}}}]}}}}, "responses": {"200": {"description": "Updated draft"}, "403": {"description": "Only author/editor may change own draft"}, "409": {"description": "Stale version"}}} + "servers": [{ "url": "/api/v2/investment/admin" }], + "security": [{ "webSession": [] }], + "paths": { + "/tax-rules": { + "get": { + "summary": "List rule versions for designated governance users", + "parameters": [ + { + "name": "rule_code", + "in": "query", + "schema": { "type": "string", "maxLength": 64 } + }, + { + "name": "component", + "in": "query", + "schema": { "$ref": "#/components/schemas/Component" } + }, + { + "name": "scope_key", + "in": "query", + "schema": { "type": "string", "maxLength": 160 } + }, + { + "name": "status", + "in": "query", + "schema": { "$ref": "#/components/schemas/Status" } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + ], + "responses": { + "200": { "description": "Paginated rule versions" }, + "403": { "description": "Role or MFA required" } + } + }, + "post": { + "summary": "Create a draft version (editor role)", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleVersionInput" + } + } + } + }, + "responses": { + "201": { "description": "Draft saved with version_no 1" }, + "403": { "description": "Editor role and MFA required" }, + "422": { "description": "Invalid source, fields, or scope" } + } + } + }, + "/tax-rules/{id}": { + "get": { + "summary": "Read a rule version and its governance state", + "parameters": [{ "$ref": "#/components/parameters/Id" }], + "responses": { + "200": { "description": "Private governance record" }, + "404": { "description": "Not found" } + } + }, + "put": { + "summary": "Edit the author's draft with optimistic locking", + "parameters": [{ "$ref": "#/components/parameters/Id" }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RuleVersionInput" + }, + { + "type": "object", + "required": ["version_no"], + "properties": { + "version_no": { + "type": "integer", + "minimum": 1 + } + } + } + ] + } + } + } + }, + "responses": { + "200": { "description": "Updated draft" }, + "403": { + "description": "Only author/editor may change own draft" + }, + "409": { "description": "Stale version" } + } + } + }, + "/tax-rules/{id}/{action}": { + "post": { + "summary": "Advance a rule through submit, review, reject, approve, publish, suspend, or archive", + "parameters": [ + { "$ref": "#/components/parameters/Id" }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "submit", + "review", + "reject", + "approve", + "publish", + "suspend", + "archive" + ] + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transition" + } + } + } + }, + "responses": { + "200": { + "description": "Transition recorded with append-only snapshot" + }, + "403": { + "description": "Role segregation denied transition" + }, + "409": { + "description": "Stale version or published period overlap" + }, + "422": { + "description": "Invalid state or unavailable legal source" + } + } + } + } }, - "/tax-rules/{id}/{action}": { - "post": { - "summary": "Advance a rule through submit, review, reject, approve, publish, suspend, or archive", - "parameters": [{"$ref": "#/components/parameters/Id"}, {"name": "action", "in": "path", "required": true, "schema": {"type": "string", "enum": ["submit", "review", "reject", "approve", "publish", "suspend", "archive"]}}], - "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Transition"}}}}, - "responses": {"200": {"description": "Transition recorded with append-only snapshot"}, "403": {"description": "Role segregation denied transition"}, "409": {"description": "Stale version or published period overlap"}, "422": {"description": "Invalid state or unavailable legal source"}} - } + "components": { + "securitySchemes": { + "webSession": { + "type": "apiKey", + "in": "cookie", + "name": "investmoz_session", + "description": "Verified portal manager, confirmed MFA, and explicit role allowlist are all required." + } + }, + "parameters": { + "Id": { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + }, + "schemas": { + "Component": { + "type": "string", + "enum": [ + "IRPC", + "IVA", + "ISPC", + "IRPS", + "INSS", + "RETENCOES", + "DIREITOS_ADUANEIROS", + "ICE" + ] + }, + "Status": { + "type": "string", + "enum": [ + "draft", + "submitted", + "reviewed", + "rejected", + "approved", + "published", + "suspended", + "archived" + ] + }, + "RuleVersionInput": { + "type": "object", + "additionalProperties": false, + "required": [ + "rule_code", + "rule_version", + "component", + "scope_key", + "valid_from", + "legal_source_id", + "source_sha256", + "rule_definition", + "parameters", + "rounding_policy" + ], + "properties": { + "rule_code": { + "type": "string", + "pattern": "^[A-Z0-9][A-Z0-9._-]{2,63}$" + }, + "rule_version": { "type": "integer", "minimum": 1 }, + "component": { "$ref": "#/components/schemas/Component" }, + "scope_key": { + "type": "string", + "minLength": 2, + "maxLength": 160 + }, + "valid_from": { "type": "string", "format": "date" }, + "valid_until": { + "type": ["string", "null"], + "format": "date" + }, + "legal_source_id": { "type": "string", "format": "uuid" }, + "source_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Hash declared by the editor; official exemplar verification remains an approval task." + }, + "rule_definition": { + "type": "object", + "description": "Versioned declarative AST; a parameter update may preserve the same AST." + }, + "parameters": { + "type": "object", + "description": "Versioned values approved with the rule version; each DSL parameter has value, unit and conditions. No values are seeded.", + "additionalProperties": { + "type": "object", + "required": ["value", "unit", "conditions"], + "properties": { + "value": { + "oneOf": [ + { "type": "string" }, + { "type": "integer" } + ] + }, + "unit": { "type": "string" }, + "conditions": { "type": "object" } + } + } + }, + "rounding_policy": { + "type": "object", + "description": "Versioned with the rule and must be approved." + } + } + }, + "Transition": { + "type": "object", + "additionalProperties": false, + "required": ["version_no", "evidence"], + "properties": { + "version_no": { "type": "integer", "minimum": 1 }, + "evidence": { + "type": "string", + "minLength": 20, + "maxLength": 2000 + } + } + } + } } - }, - "components": { - "securitySchemes": {"webSession": {"type": "apiKey", "in": "cookie", "name": "investmoz_session", "description": "Verified portal manager, confirmed MFA, and explicit role allowlist are all required."}}, - "parameters": {"Id": {"name": "id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}}, - "schemas": { - "Component": {"type": "string", "enum": ["IRPC", "IVA", "ISPC", "IRPS", "INSS", "RETENCOES", "DIREITOS_ADUANEIROS", "ICE"]}, - "Status": {"type": "string", "enum": ["draft", "submitted", "reviewed", "rejected", "approved", "published", "suspended", "archived"]}, - "RuleVersionInput": {"type": "object", "additionalProperties": false, "required": ["rule_code", "rule_version", "component", "scope_key", "valid_from", "legal_source_id", "source_sha256", "rule_definition", "parameters", "rounding_policy"], "properties": {"rule_code": {"type": "string", "pattern": "^[A-Z0-9][A-Z0-9._-]{2,63}$"}, "rule_version": {"type": "integer", "minimum": 1}, "component": {"$ref": "#/components/schemas/Component"}, "scope_key": {"type": "string", "minLength": 2, "maxLength": 160}, "valid_from": {"type": "string", "format": "date"}, "valid_until": {"type": ["string", "null"], "format": "date"}, "legal_source_id": {"type": "string", "format": "uuid"}, "source_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Hash declared by the editor; official exemplar verification remains an approval task."}, "rule_definition": {"type": "object", "description": "Versioned declarative AST; a parameter update may preserve the same AST."}, "parameters": {"type": "object", "description": "Versioned values approved with the rule version; each DSL parameter has value, unit and conditions. No values are seeded.", "additionalProperties": {"type": "object", "required": ["value", "unit", "conditions"], "properties": {"value": {"oneOf": [{"type": "string"}, {"type": "integer"}]}, "unit": {"type": "string"}, "conditions": {"type": "object"}}}}, "rounding_policy": {"type": "object", "description": "Versioned with the rule and must be approved."}}}, - "Transition": {"type": "object", "additionalProperties": false, "required": ["version_no", "evidence"], "properties": {"version_no": {"type": "integer", "minimum": 1}, "evidence": {"type": "string", "minLength": 20, "maxLength": 2000}}} - } - } } diff --git a/docs/investmoz/simulations/simulation-draft-continuity-active-browser-2026-10-04.json b/docs/investmoz/simulations/simulation-draft-continuity-active-browser-2026-10-04.json index acbf9b25..c1630dbb 100644 --- a/docs/investmoz/simulations/simulation-draft-continuity-active-browser-2026-10-04.json +++ b/docs/investmoz/simulations/simulation-draft-continuity-active-browser-2026-10-04.json @@ -1,36 +1,36 @@ { - "url": "http://127.0.0.1:8000/Simulador", - "browser": "Microsoft Edge headless", - "database": "investemoz (base activa do portal)", - "initialPageStatus": 200, - "createdStatus": 201, - "draftId": "46f2d0b5-446a-4a3f-b3b6-726bbe9780bd", - "cookieExpiryOnCreate": 1793717926.442219, - "recoveredAfterBrowserRestart": true, - "recoveredTitle": "Ensaio activo final", - "automaticReadKeptCookieExpiry": true, - "explicitContinueLaterStatus": 200, - "explicitContinueLaterExpiry": "2026-11-03T14:58:57.000000Z", - "cookieExpiryAfterContinueLater": 1793717937.747094, - "cookieAndServerExpiryDifferenceSeconds": 0.747093915939331, - "forgetButtonVisible": true, - "forgetStatus": 204, - "recoveryCookieAfterForgetPresent": false, - "browserErrors": [], - "activeDatabaseVerification": { - "database": "investemoz", - "preMigrationDraftCount": 14, - "preMigrationAnonymousCount": 13, - "preMigrationAccountOwnedCount": 1, - "existingAnonymousDraftsPreserved": true, - "activeAnonymousDraftsAfterChecks": 14, - "activeAccountOwnedDraftsAfterChecks": 1, - "syntheticBrowserDraftsSoftDeleted": 3, - "unattributedDraftLeftUntouched": true - }, - "screenshots": [ - "simulation-draft-continuity-active-before-forget-2026-10-04.png", - "simulation-draft-continuity-active-after-forget-2026-10-04.png" - ], - "allChecksPassed": true + "url": "http://127.0.0.1:8000/Simulador", + "browser": "Microsoft Edge headless", + "database": "investemoz (base activa do portal)", + "initialPageStatus": 200, + "createdStatus": 201, + "draftId": "46f2d0b5-446a-4a3f-b3b6-726bbe9780bd", + "cookieExpiryOnCreate": 1793717926.442219, + "recoveredAfterBrowserRestart": true, + "recoveredTitle": "Ensaio activo final", + "automaticReadKeptCookieExpiry": true, + "explicitContinueLaterStatus": 200, + "explicitContinueLaterExpiry": "2026-11-03T14:58:57.000000Z", + "cookieExpiryAfterContinueLater": 1793717937.747094, + "cookieAndServerExpiryDifferenceSeconds": 0.747093915939331, + "forgetButtonVisible": true, + "forgetStatus": 204, + "recoveryCookieAfterForgetPresent": false, + "browserErrors": [], + "activeDatabaseVerification": { + "database": "investemoz", + "preMigrationDraftCount": 14, + "preMigrationAnonymousCount": 13, + "preMigrationAccountOwnedCount": 1, + "existingAnonymousDraftsPreserved": true, + "activeAnonymousDraftsAfterChecks": 14, + "activeAccountOwnedDraftsAfterChecks": 1, + "syntheticBrowserDraftsSoftDeleted": 3, + "unattributedDraftLeftUntouched": true + }, + "screenshots": [ + "simulation-draft-continuity-active-before-forget-2026-10-04.png", + "simulation-draft-continuity-active-after-forget-2026-10-04.png" + ], + "allChecksPassed": true } diff --git a/docs/investmoz/simulations/simulator-a11y-browser-2026-10-04.json b/docs/investmoz/simulations/simulator-a11y-browser-2026-10-04.json index 6e2d9be9..6c268a3a 100644 --- a/docs/investmoz/simulations/simulator-a11y-browser-2026-10-04.json +++ b/docs/investmoz/simulations/simulator-a11y-browser-2026-10-04.json @@ -1,136 +1,136 @@ { - "checkedAt": "2026-10-04T08:04:11.078Z", - "route": "http://127.0.0.1:18082/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", - "title": "Simulador Fiscal - Laravel", - "dimensions": [ - { - "viewport": 390, - "document": 390, - "documentScroll": 390, - "bodyScroll": 390, - "fixedControls": 0, - "controls": 19 - }, - { - "viewport": 768, - "document": 753, - "documentScroll": 753, - "bodyScroll": 753, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1024, - "document": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1440, - "document": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "fixedControls": 0, - "controls": 18 - } - ], - "keyboardFocusSequence": [ - { - "tag": "button", - "name": "1. Finalidade", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "2. Perfil do promotor", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "3. Identificação e contactos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "4. Projecto ou negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "5. Localização", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "6. Estrutura financeira", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "7. Emprego e impacto", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "8. Operação fiscal", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "9. Necessidade de negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "10. Documentos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "11. Revisão", - "visible": true, - "focusVisible": true - }, - { - "tag": "input", - "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", - "visible": true, - "focusVisible": true - } - ], - "invalidKeyboardFocus": [], - "visibleControls": 5, - "visibleButtons": 14, - "visibleLinks": 6, - "unlabeledControls": [], - "unnamedButtons": [], - "unnamedLinks": [], - "unnamedAxControls": [], - "visibleAlerts": [], - "javascriptExceptions": [], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_test:55441" - } - ], - "apiFailures": [], - "expectedQaTarget": "investmoz_test:55441", - "qaTargetVerified": true, - "passed": true + "checkedAt": "2026-10-04T08:04:11.078Z", + "route": "http://127.0.0.1:18082/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", + "title": "Simulador Fiscal - Laravel", + "dimensions": [ + { + "viewport": 390, + "document": 390, + "documentScroll": 390, + "bodyScroll": 390, + "fixedControls": 0, + "controls": 19 + }, + { + "viewport": 768, + "document": 753, + "documentScroll": 753, + "bodyScroll": 753, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1024, + "document": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1440, + "document": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "fixedControls": 0, + "controls": 18 + } + ], + "keyboardFocusSequence": [ + { + "tag": "button", + "name": "1. Finalidade", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "2. Perfil do promotor", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "3. Identificação e contactos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "4. Projecto ou negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "5. Localização", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "6. Estrutura financeira", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "7. Emprego e impacto", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "8. Operação fiscal", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "9. Necessidade de negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "10. Documentos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "11. Revisão", + "visible": true, + "focusVisible": true + }, + { + "tag": "input", + "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", + "visible": true, + "focusVisible": true + } + ], + "invalidKeyboardFocus": [], + "visibleControls": 5, + "visibleButtons": 14, + "visibleLinks": 6, + "unlabeledControls": [], + "unnamedButtons": [], + "unnamedLinks": [], + "unnamedAxControls": [], + "visibleAlerts": [], + "javascriptExceptions": [], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_test:55441" + } + ], + "apiFailures": [], + "expectedQaTarget": "investmoz_test:55441", + "qaTargetVerified": true, + "passed": true } diff --git a/docs/investmoz/simulations/simulator-api-errors-browser-2026-10-03.json b/docs/investmoz/simulations/simulator-api-errors-browser-2026-10-03.json index 8f30154f..5a7aaaf7 100644 --- a/docs/investmoz/simulations/simulator-api-errors-browser-2026-10-03.json +++ b/docs/investmoz/simulations/simulator-api-errors-browser-2026-10-03.json @@ -1,53 +1,53 @@ { - "checkedAt": "2026-10-03T12:01:07.810Z", - "route": "http://127.0.0.1:18088/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "qaTarget": "investmoz_browser_e2e_test:55443", - "scope": "real public page/current-draft GET on isolated QA; catalog, calculation, polling and draft writes intercepted in browser", - "catalogErrorState": { - "hasError": true, - "hasEmptyMessage": false, - "retryVisible": true - }, - "result": { - "mocks": { - "catalog": 2, - "created": 1, - "poll": 1, - "draftWrites": 2 + "checkedAt": "2026-10-03T12:01:07.810Z", + "route": "http://127.0.0.1:18088/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "qaTarget": "investmoz_browser_e2e_test:55443", + "scope": "real public page/current-draft GET on isolated QA; catalog, calculation, polling and draft writes intercepted in browser", + "catalogErrorState": { + "hasError": true, + "hasEmptyMessage": false, + "retryVisible": true }, - "catalogErrorAlert": false, - "pollingErrorAlert": true, - "pendingStatusVisible": true, - "networkApiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - }, - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - } - ] - }, - "serverResponses": [ - { - "path": "/simulador-fiscal", - "status": 200, - "qaTarget": "investmoz_browser_e2e_test:55443" + "result": { + "mocks": { + "catalog": 2, + "created": 1, + "poll": 1, + "draftWrites": 2 + }, + "catalogErrorAlert": false, + "pollingErrorAlert": true, + "pendingStatusVisible": true, + "networkApiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + }, + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + } + ] }, - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_browser_e2e_test:55443" - }, - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_browser_e2e_test:55443" - } - ], - "javascriptExceptions": [], - "consoleErrors": [], - "passed": true + "serverResponses": [ + { + "path": "/simulador-fiscal", + "status": 200, + "qaTarget": "investmoz_browser_e2e_test:55443" + }, + { + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_browser_e2e_test:55443" + }, + { + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_browser_e2e_test:55443" + } + ], + "javascriptExceptions": [], + "consoleErrors": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-qa-verified.json b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-qa-verified.json index 46fa9bc6..3b570d70 100644 --- a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-qa-verified.json +++ b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-qa-verified.json @@ -1,136 +1,136 @@ { - "checkedAt": "2026-10-03T13:54:49.258Z", - "route": "http://127.0.0.1:18089/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", - "title": "Simulador Fiscal - Laravel", - "dimensions": [ - { - "viewport": 390, - "document": 390, - "documentScroll": 390, - "bodyScroll": 390, - "fixedControls": 0, - "controls": 19 - }, - { - "viewport": 768, - "document": 753, - "documentScroll": 753, - "bodyScroll": 753, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1024, - "document": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1440, - "document": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "fixedControls": 0, - "controls": 18 - } - ], - "keyboardFocusSequence": [ - { - "tag": "button", - "name": "1. Finalidade", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "2. Perfil do promotor", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "3. Identificação e contactos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "4. Projecto ou negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "5. Localização", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "6. Estrutura financeira", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "7. Emprego e impacto", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "8. Operação fiscal", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "9. Necessidade de negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "10. Documentos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "11. Revisão", - "visible": true, - "focusVisible": true - }, - { - "tag": "input", - "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", - "visible": true, - "focusVisible": true - } - ], - "invalidKeyboardFocus": [], - "visibleControls": 5, - "visibleButtons": 14, - "visibleLinks": 6, - "unlabeledControls": [], - "unnamedButtons": [], - "unnamedLinks": [], - "unnamedAxControls": [], - "visibleAlerts": [], - "javascriptExceptions": [], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_test:55441" - } - ], - "apiFailures": [], - "expectedQaTarget": "investmoz_test:55441", - "qaTargetVerified": true, - "passed": true + "checkedAt": "2026-10-03T13:54:49.258Z", + "route": "http://127.0.0.1:18089/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", + "title": "Simulador Fiscal - Laravel", + "dimensions": [ + { + "viewport": 390, + "document": 390, + "documentScroll": 390, + "bodyScroll": 390, + "fixedControls": 0, + "controls": 19 + }, + { + "viewport": 768, + "document": 753, + "documentScroll": 753, + "bodyScroll": 753, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1024, + "document": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1440, + "document": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "fixedControls": 0, + "controls": 18 + } + ], + "keyboardFocusSequence": [ + { + "tag": "button", + "name": "1. Finalidade", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "2. Perfil do promotor", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "3. Identificação e contactos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "4. Projecto ou negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "5. Localização", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "6. Estrutura financeira", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "7. Emprego e impacto", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "8. Operação fiscal", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "9. Necessidade de negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "10. Documentos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "11. Revisão", + "visible": true, + "focusVisible": true + }, + { + "tag": "input", + "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", + "visible": true, + "focusVisible": true + } + ], + "invalidKeyboardFocus": [], + "visibleControls": 5, + "visibleButtons": 14, + "visibleLinks": 6, + "unlabeledControls": [], + "unnamedButtons": [], + "unnamedLinks": [], + "unnamedAxControls": [], + "visibleAlerts": [], + "javascriptExceptions": [], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_test:55441" + } + ], + "apiFailures": [], + "expectedQaTarget": "investmoz_test:55441", + "qaTargetVerified": true, + "passed": true } diff --git a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-runtime-verified.json b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-runtime-verified.json index 8ab1f322..25bc2042 100644 --- a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-runtime-verified.json +++ b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-runtime-verified.json @@ -1,136 +1,136 @@ { - "checkedAt": "2026-10-03T13:57:53.343Z", - "route": "http://127.0.0.1:18089/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", - "title": "Simulador Fiscal - Laravel", - "dimensions": [ - { - "viewport": 390, - "document": 390, - "documentScroll": 390, - "bodyScroll": 390, - "fixedControls": 0, - "controls": 19 - }, - { - "viewport": 768, - "document": 753, - "documentScroll": 753, - "bodyScroll": 753, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1024, - "document": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1440, - "document": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "fixedControls": 0, - "controls": 18 - } - ], - "keyboardFocusSequence": [ - { - "tag": "button", - "name": "1. Finalidade", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "2. Perfil do promotor", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "3. Identificação e contactos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "4. Projecto ou negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "5. Localização", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "6. Estrutura financeira", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "7. Emprego e impacto", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "8. Operação fiscal", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "9. Necessidade de negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "10. Documentos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "11. Revisão", - "visible": true, - "focusVisible": true - }, - { - "tag": "input", - "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", - "visible": true, - "focusVisible": true - } - ], - "invalidKeyboardFocus": [], - "visibleControls": 5, - "visibleButtons": 14, - "visibleLinks": 6, - "unlabeledControls": [], - "unnamedButtons": [], - "unnamedLinks": [], - "unnamedAxControls": [], - "visibleAlerts": [], - "javascriptExceptions": [], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_test:55441" - } - ], - "apiFailures": [], - "expectedQaTarget": "investmoz_test:55441", - "qaTargetVerified": true, - "passed": true + "checkedAt": "2026-10-03T13:57:53.343Z", + "route": "http://127.0.0.1:18089/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", + "title": "Simulador Fiscal - Laravel", + "dimensions": [ + { + "viewport": 390, + "document": 390, + "documentScroll": 390, + "bodyScroll": 390, + "fixedControls": 0, + "controls": 19 + }, + { + "viewport": 768, + "document": 753, + "documentScroll": 753, + "bodyScroll": 753, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1024, + "document": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1440, + "document": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "fixedControls": 0, + "controls": 18 + } + ], + "keyboardFocusSequence": [ + { + "tag": "button", + "name": "1. Finalidade", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "2. Perfil do promotor", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "3. Identificação e contactos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "4. Projecto ou negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "5. Localização", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "6. Estrutura financeira", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "7. Emprego e impacto", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "8. Operação fiscal", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "9. Necessidade de negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "10. Documentos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "11. Revisão", + "visible": true, + "focusVisible": true + }, + { + "tag": "input", + "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", + "visible": true, + "focusVisible": true + } + ], + "invalidKeyboardFocus": [], + "visibleControls": 5, + "visibleButtons": 14, + "visibleLinks": 6, + "unlabeledControls": [], + "unnamedButtons": [], + "unnamedLinks": [], + "unnamedAxControls": [], + "visibleAlerts": [], + "javascriptExceptions": [], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_test:55441" + } + ], + "apiFailures": [], + "expectedQaTarget": "investmoz_test:55441", + "qaTargetVerified": true, + "passed": true } diff --git a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun.json b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun.json index 81fe628b..521b7b42 100644 --- a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun.json +++ b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun.json @@ -1,133 +1,133 @@ { - "checkedAt": "2026-10-03T13:54:22.547Z", - "route": "http://127.0.0.1:18089/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", - "title": "Simulador Fiscal - Laravel", - "dimensions": [ - { - "viewport": 390, - "document": 390, - "documentScroll": 390, - "bodyScroll": 390, - "fixedControls": 0, - "controls": 19 - }, - { - "viewport": 768, - "document": 753, - "documentScroll": 753, - "bodyScroll": 753, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1024, - "document": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1440, - "document": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "fixedControls": 0, - "controls": 18 - } - ], - "keyboardFocusSequence": [ - { - "tag": "button", - "name": "1. Finalidade", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "2. Perfil do promotor", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "3. Identificação e contactos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "4. Projecto ou negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "5. Localização", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "6. Estrutura financeira", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "7. Emprego e impacto", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "8. Operação fiscal", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "9. Necessidade de negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "10. Documentos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "11. Revisão", - "visible": true, - "focusVisible": true - }, - { - "tag": "input", - "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", - "visible": true, - "focusVisible": true - } - ], - "invalidKeyboardFocus": [], - "visibleControls": 5, - "visibleButtons": 14, - "visibleLinks": 6, - "unlabeledControls": [], - "unnamedButtons": [], - "unnamedLinks": [], - "unnamedAxControls": [], - "visibleAlerts": [], - "javascriptExceptions": [], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - } - ], - "apiFailures": [], - "passed": true + "checkedAt": "2026-10-03T13:54:22.547Z", + "route": "http://127.0.0.1:18089/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", + "title": "Simulador Fiscal - Laravel", + "dimensions": [ + { + "viewport": 390, + "document": 390, + "documentScroll": 390, + "bodyScroll": 390, + "fixedControls": 0, + "controls": 19 + }, + { + "viewport": 768, + "document": 753, + "documentScroll": 753, + "bodyScroll": 753, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1024, + "document": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1440, + "document": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "fixedControls": 0, + "controls": 18 + } + ], + "keyboardFocusSequence": [ + { + "tag": "button", + "name": "1. Finalidade", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "2. Perfil do promotor", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "3. Identificação e contactos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "4. Projecto ou negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "5. Localização", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "6. Estrutura financeira", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "7. Emprego e impacto", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "8. Operação fiscal", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "9. Necessidade de negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "10. Documentos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "11. Revisão", + "visible": true, + "focusVisible": true + }, + { + "tag": "input", + "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", + "visible": true, + "focusVisible": true + } + ], + "invalidKeyboardFocus": [], + "visibleControls": 5, + "visibleButtons": 14, + "visibleLinks": 6, + "unlabeledControls": [], + "unnamedButtons": [], + "unnamedLinks": [], + "unnamedAxControls": [], + "visibleAlerts": [], + "javascriptExceptions": [], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + } + ], + "apiFailures": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03.json b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03.json index 57380b3a..ecd22ab2 100644 --- a/docs/investmoz/simulations/simulator-browser-audit-2026-10-03.json +++ b/docs/investmoz/simulations/simulator-browser-audit-2026-10-03.json @@ -1,133 +1,133 @@ { - "checkedAt": "2026-10-03T08:35:39.872Z", - "route": "http://127.0.0.1:18082/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", - "title": "Simulador Fiscal - Laravel", - "dimensions": [ - { - "viewport": 390, - "document": 390, - "documentScroll": 390, - "bodyScroll": 390, - "fixedControls": 0, - "controls": 19 - }, - { - "viewport": 768, - "document": 753, - "documentScroll": 753, - "bodyScroll": 753, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1024, - "document": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "fixedControls": 0, - "controls": 18 - }, - { - "viewport": 1440, - "document": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "fixedControls": 0, - "controls": 18 - } - ], - "keyboardFocusSequence": [ - { - "tag": "button", - "name": "1. Finalidade", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "2. Perfil do promotor", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "3. Identificação e contactos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "4. Projecto ou negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "5. Localização", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "6. Estrutura financeira", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "7. Emprego e impacto", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "8. Operação fiscal", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "9. Necessidade de negócio", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "10. Documentos", - "visible": true, - "focusVisible": true - }, - { - "tag": "button", - "name": "11. Revisão", - "visible": true, - "focusVisible": true - }, - { - "tag": "input", - "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", - "visible": true, - "focusVisible": true - } - ], - "invalidKeyboardFocus": [], - "visibleControls": 5, - "visibleButtons": 14, - "visibleLinks": 6, - "unlabeledControls": [], - "unnamedButtons": [], - "unnamedLinks": [], - "unnamedAxControls": [], - "visibleAlerts": [], - "javascriptExceptions": [], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - } - ], - "apiFailures": [], - "passed": true + "checkedAt": "2026-10-03T08:35:39.872Z", + "route": "http://127.0.0.1:18082/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "UI, foco e GET público de rascunho; sem cálculo, escrita ou validação de conteúdo persistido", + "title": "Simulador Fiscal - Laravel", + "dimensions": [ + { + "viewport": 390, + "document": 390, + "documentScroll": 390, + "bodyScroll": 390, + "fixedControls": 0, + "controls": 19 + }, + { + "viewport": 768, + "document": 753, + "documentScroll": 753, + "bodyScroll": 753, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1024, + "document": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "fixedControls": 0, + "controls": 18 + }, + { + "viewport": 1440, + "document": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "fixedControls": 0, + "controls": 18 + } + ], + "keyboardFocusSequence": [ + { + "tag": "button", + "name": "1. Finalidade", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "2. Perfil do promotor", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "3. Identificação e contactos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "4. Projecto ou negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "5. Localização", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "6. Estrutura financeira", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "7. Emprego e impacto", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "8. Operação fiscal", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "9. Necessidade de negócio", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "10. Documentos", + "visible": true, + "focusVisible": true + }, + { + "tag": "button", + "name": "11. Revisão", + "visible": true, + "focusVisible": true + }, + { + "tag": "input", + "name": "O que pretende calcular ou planear? Escolha o percurso (obrigatório)", + "visible": true, + "focusVisible": true + } + ], + "invalidKeyboardFocus": [], + "visibleControls": 5, + "visibleButtons": 14, + "visibleLinks": 6, + "unlabeledControls": [], + "unnamedButtons": [], + "unnamedLinks": [], + "unnamedAxControls": [], + "visibleAlerts": [], + "javascriptExceptions": [], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + } + ], + "apiFailures": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-calculation-browser-2026-10-03.json b/docs/investmoz/simulations/simulator-calculation-browser-2026-10-03.json index aea282f9..cc47ec79 100644 --- a/docs/investmoz/simulations/simulator-calculation-browser-2026-10-03.json +++ b/docs/investmoz/simulations/simulator-calculation-browser-2026-10-03.json @@ -1,104 +1,98 @@ { - "checkedAt": "2026-10-03T10:20:03.943Z", - "route": "http://127.0.0.1:18086/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "submissão anónima visível, execução aritmética e leitura do resultado; sem dados pessoais ou interpretação fiscal", - "inputs": { - "fiscalPeriod": "2026", - "componentContext": "inss", - "base": "1000", - "rate": "10", - "reduction": "50" - }, - "clientValidation": { - "inputInvalid": true, - "inputDescribedBy": [ - "error-base" + "checkedAt": "2026-10-03T10:20:03.943Z", + "route": "http://127.0.0.1:18086/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "submissão anónima visível, execução aritmética e leitura do resultado; sem dados pessoais ou interpretação fiscal", + "inputs": { + "fiscalPeriod": "2026", + "componentContext": "inss", + "base": "1000", + "rate": "10", + "reduction": "50" + }, + "clientValidation": { + "inputInvalid": true, + "inputDescribedBy": ["error-base"], + "descriptionsExist": true, + "descriptionsAreAlerts": true, + "activeField": "field-base", + "calculationRequestsBeforeCorrection": 0, + "accessibleErrors": true + }, + "expectedArithmeticValue": "50", + "title": "Simulador Fiscal - Laravel", + "resultText": [ + "IRPS", + "INSS", + "IVA", + "IRPC", + "ISPC", + "Retenções", + "50.000000000000000000000000000000000000" ], - "descriptionsExist": true, - "descriptionsAreAlerts": true, - "activeField": "field-base", - "calculationRequestsBeforeCorrection": 0, - "accessibleErrors": true - }, - "expectedArithmeticValue": "50", - "title": "Simulador Fiscal - Laravel", - "resultText": [ - "IRPS", - "INSS", - "IVA", - "IRPC", - "ISPC", - "Retenções", - "50.000000000000000000000000000000000000" - ], - "completed": true, - "taxDisclaimer": true, - "visibleAlerts": [], - "anonymousIdentityNotice": true, - "calculationRequests": [ - { - "path": "/api/v1/simulations", - "status": 202 - } - ], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - }, - { - "path": "/api/v1/simulations", - "status": 202 - }, - { - "path": "/api/v1/simulation-drafts/current", - "status": 200 - }, - { - "path": "/api/v1/simulation-drafts", - "status": 201 - }, - { - "path": "/api/v1/simulations/d466c603-46bb-4b7e-ba1c-8bdb704ec735", - "status": 200 - } - ], - "simulationResponseShapes": [ - { - "httpStatus": 202, - "outerKeys": [ - "access_token", - "data", - "replayed" - ], - "replayedType": "boolean", - "replayed": false, - "accessTokenLength": 64, - "data": { - "keys": [ - "anonymous_token_hash", - "calculation_snapshot", - "created_at", - "id", - "input_snapshot", - "organization_id", - "owner_user_id", - "questionnaire_version_id", - "reports", - "scenarios", - "status", - "updated_at", - "version_no" - ], - "idType": "string", - "status": "queued", - "versionNoType": "number", - "resultSnapshotType": "undefined" - } - } - ], - "failedResponses": [], - "javascriptExceptions": [], - "passed": true + "completed": true, + "taxDisclaimer": true, + "visibleAlerts": [], + "anonymousIdentityNotice": true, + "calculationRequests": [ + { + "path": "/api/v1/simulations", + "status": 202 + } + ], + "apiResponses": [ + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + }, + { + "path": "/api/v1/simulations", + "status": 202 + }, + { + "path": "/api/v1/simulation-drafts/current", + "status": 200 + }, + { + "path": "/api/v1/simulation-drafts", + "status": 201 + }, + { + "path": "/api/v1/simulations/d466c603-46bb-4b7e-ba1c-8bdb704ec735", + "status": 200 + } + ], + "simulationResponseShapes": [ + { + "httpStatus": 202, + "outerKeys": ["access_token", "data", "replayed"], + "replayedType": "boolean", + "replayed": false, + "accessTokenLength": 64, + "data": { + "keys": [ + "anonymous_token_hash", + "calculation_snapshot", + "created_at", + "id", + "input_snapshot", + "organization_id", + "owner_user_id", + "questionnaire_version_id", + "reports", + "scenarios", + "status", + "updated_at", + "version_no" + ], + "idType": "string", + "status": "queued", + "versionNoType": "number", + "resultSnapshotType": "undefined" + } + } + ], + "failedResponses": [], + "javascriptExceptions": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-calculation-responsive-browser-2026-10-03.json b/docs/investmoz/simulations/simulator-calculation-responsive-browser-2026-10-03.json index 4ae29ad1..4159cbb0 100644 --- a/docs/investmoz/simulations/simulator-calculation-responsive-browser-2026-10-03.json +++ b/docs/investmoz/simulations/simulator-calculation-responsive-browser-2026-10-03.json @@ -1,946 +1,940 @@ { - "checkedAt": "2026-10-03T14:44:45.817Z", - "route": "http://127.0.0.1:18089/simulador-fiscal", - "browser": "Microsoft Edge headless via CDP", - "scope": "submissão anónima, validação acessível e UI responsiva nos estados finalidade/fiscal/resultado; sem dados pessoais ou interpretação fiscal", - "expectedQaTarget": "investmoz_test:55441", - "qaTargetVerified": true, - "inputs": { - "fiscalPeriod": "2026", - "componentContext": "inss", - "base": "1000", - "rate": "10", - "reduction": "50" - }, - "clientValidation": { - "inputInvalid": true, - "inputDescribedBy": [ - "error-base" + "checkedAt": "2026-10-03T14:44:45.817Z", + "route": "http://127.0.0.1:18089/simulador-fiscal", + "browser": "Microsoft Edge headless via CDP", + "scope": "submissão anónima, validação acessível e UI responsiva nos estados finalidade/fiscal/resultado; sem dados pessoais ou interpretação fiscal", + "expectedQaTarget": "investmoz_test:55441", + "qaTargetVerified": true, + "inputs": { + "fiscalPeriod": "2026", + "componentContext": "inss", + "base": "1000", + "rate": "10", + "reduction": "50" + }, + "clientValidation": { + "inputInvalid": true, + "inputDescribedBy": ["error-base"], + "descriptionsExist": true, + "descriptionsAreAlerts": true, + "activeField": "field-base", + "calculationRequestsBeforeCorrection": 0, + "accessibleErrors": true + }, + "fiscalHeadingFocus": { + "focused": true, + "outlineStyle": "solid", + "outlineWidth": "2px", + "visible": true, + "visibleIndicator": true + }, + "responsiveStates": [ + { + "state": "purpose", + "viewports": [ + { + "requestedWidth": 320, + "viewport": 320, + "layoutWidth": 320, + "documentScroll": 320, + "bodyScroll": 320, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 1749, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1168, + "right": 1307, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 360, + "viewport": 360, + "layoutWidth": 360, + "documentScroll": 360, + "bodyScroll": 360, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 1749, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1168, + "right": 1307, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 390, + "viewport": 390, + "layoutWidth": 390, + "documentScroll": 390, + "bodyScroll": 390, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 1749, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1657, + "right": 1749, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1519, + "right": 1649, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1315, + "right": 1511, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1168, + "right": 1307, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 768, + "viewport": 768, + "layoutWidth": 753, + "documentScroll": 753, + "bodyScroll": 753, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 45, + "right": 1757, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1665, + "right": 1757, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1665, + "right": 1757, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1527, + "right": 1657, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1527, + "right": 1657, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1323, + "right": 1519, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1323, + "right": 1519, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1176, + "right": 1315, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1024, + "viewport": 1024, + "layoutWidth": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 45, + "right": 1757, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1665, + "right": 1757, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1665, + "right": 1757, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1527, + "right": 1657, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1527, + "right": 1657, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1323, + "right": 1519, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1323, + "right": 1519, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1176, + "right": 1315, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1440, + "viewport": 1440, + "layoutWidth": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 5, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 182, + "right": 1893, + "width": 1712, + "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1801, + "right": 1893, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1801, + "right": 1893, + "width": 92, + "text": "11. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1664, + "right": 1793, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1664, + "right": 1793, + "width": 129, + "text": "10. Documentos" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1459, + "right": 1656, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 1459, + "right": 1656, + "width": 197, + "text": "9. Necessidade de negócio" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 1312, + "right": 1451, + "width": 139, + "text": "8. Operação fiscal" + } + ], + "unnamedAxControls": [], + "passed": true + } + ], + "passed": true + }, + { + "state": "tax", + "viewports": [ + { + "requestedWidth": 320, + "viewport": 320, + "layoutWidth": 320, + "documentScroll": 320, + "bodyScroll": 320, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 385, + "width": 348, + "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 360, + "viewport": 360, + "layoutWidth": 360, + "documentScroll": 360, + "bodyScroll": 360, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 385, + "width": 348, + "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 390, + "viewport": 390, + "layoutWidth": 390, + "documentScroll": 390, + "bodyScroll": 390, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 768, + "viewport": 768, + "layoutWidth": 753, + "documentScroll": 753, + "bodyScroll": 753, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1024, + "viewport": 1024, + "layoutWidth": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1440, + "viewport": 1440, + "layoutWidth": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 34, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + } + ], + "passed": true + }, + { + "state": "result", + "viewports": [ + { + "requestedWidth": 320, + "viewport": 320, + "layoutWidth": 320, + "documentScroll": 320, + "bodyScroll": 320, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 385, + "width": 348, + "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 360, + "viewport": 360, + "layoutWidth": 360, + "documentScroll": 360, + "bodyScroll": 360, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [ + { + "tag": "OL", + "id": null, + "className": "flex min-w-max gap-2", + "left": 37, + "right": 385, + "width": 348, + "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" + }, + { + "tag": "LI", + "id": null, + "className": "", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + }, + { + "tag": "BUTTON", + "id": null, + "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", + "left": 296, + "right": 385, + "width": 89, + "text": "3. Revisão" + } + ], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 390, + "viewport": 390, + "layoutWidth": 390, + "documentScroll": 390, + "bodyScroll": 390, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 768, + "viewport": 768, + "layoutWidth": 753, + "documentScroll": 753, + "bodyScroll": 753, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1024, + "viewport": 1024, + "layoutWidth": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + }, + { + "requestedWidth": 1440, + "viewport": 1440, + "layoutWidth": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "unnamedControls": [], + "unnamedButtons": 0, + "unnamedLinks": 0, + "visibleControls": 37, + "overflowElements": [], + "unnamedAxControls": [], + "passed": true + } + ], + "passed": true + } ], - "descriptionsExist": true, - "descriptionsAreAlerts": true, - "activeField": "field-base", - "calculationRequestsBeforeCorrection": 0, - "accessibleErrors": true - }, - "fiscalHeadingFocus": { - "focused": true, - "outlineStyle": "solid", - "outlineWidth": "2px", - "visible": true, - "visibleIndicator": true - }, - "responsiveStates": [ - { - "state": "purpose", - "viewports": [ + "expectedArithmeticValue": "50", + "title": "Simulador Fiscal - Laravel", + "resultText": [ + "IRPS", + "INSS", + "IVA", + "IRPC", + "ISPC", + "Retenções", + "50.000000000000000000000000000000000000" + ], + "completed": true, + "taxDisclaimer": true, + "visibleAlerts": [], + "anonymousIdentityNotice": true, + "calculationRequests": [ { - "requestedWidth": 320, - "viewport": 320, - "layoutWidth": 320, - "documentScroll": 320, - "bodyScroll": 320, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 1749, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1168, - "right": 1307, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 360, - "viewport": 360, - "layoutWidth": 360, - "documentScroll": 360, - "bodyScroll": 360, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 1749, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1168, - "right": 1307, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 390, - "viewport": 390, - "layoutWidth": 390, - "documentScroll": 390, - "bodyScroll": 390, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 1749, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1657, - "right": 1749, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1519, - "right": 1649, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1315, - "right": 1511, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1168, - "right": 1307, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 768, - "viewport": 768, - "layoutWidth": 753, - "documentScroll": 753, - "bodyScroll": 753, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 45, - "right": 1757, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1665, - "right": 1757, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1665, - "right": 1757, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1527, - "right": 1657, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1527, - "right": 1657, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1323, - "right": 1519, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1323, - "right": 1519, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1176, - "right": 1315, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 1024, - "viewport": 1024, - "layoutWidth": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 45, - "right": 1757, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1665, - "right": 1757, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1665, - "right": 1757, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1527, - "right": 1657, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1527, - "right": 1657, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1323, - "right": 1519, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1323, - "right": 1519, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1176, - "right": 1315, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 1440, - "viewport": 1440, - "layoutWidth": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 5, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 182, - "right": 1893, - "width": 1712, - "text": "1. Finalidade\n2. Perfil do promotor\n3. Identificação e contactos\n4. Projecto ou " - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1801, - "right": 1893, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1801, - "right": 1893, - "width": 92, - "text": "11. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1664, - "right": 1793, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1664, - "right": 1793, - "width": 129, - "text": "10. Documentos" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1459, - "right": 1656, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 1459, - "right": 1656, - "width": 197, - "text": "9. Necessidade de negócio" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 1312, - "right": 1451, - "width": 139, - "text": "8. Operação fiscal" - } - ], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulations", + "status": 202, + "qaTarget": "investmoz_test:55441" } - ], - "passed": true - }, - { - "state": "tax", - "viewports": [ + ], + "apiResponses": [ { - "requestedWidth": 320, - "viewport": 320, - "layoutWidth": 320, - "documentScroll": 320, - "bodyScroll": 320, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 385, - "width": 348, - "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - } - ], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_test:55441" }, { - "requestedWidth": 360, - "viewport": 360, - "layoutWidth": 360, - "documentScroll": 360, - "bodyScroll": 360, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 385, - "width": 348, - "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - } - ], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulation-drafts/current", + "status": 200, + "qaTarget": "investmoz_test:55441" }, { - "requestedWidth": 390, - "viewport": 390, - "layoutWidth": 390, - "documentScroll": 390, - "bodyScroll": 390, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulation-drafts", + "status": 201, + "qaTarget": "investmoz_test:55441" }, { - "requestedWidth": 768, - "viewport": 768, - "layoutWidth": 753, - "documentScroll": 753, - "bodyScroll": 753, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulations", + "status": 202, + "qaTarget": "investmoz_test:55441" }, { - "requestedWidth": 1024, - "viewport": 1024, - "layoutWidth": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulation-drafts/8286b973-eecb-44ac-8579-2413f5ade5bc", + "status": 200, + "qaTarget": "investmoz_test:55441" }, { - "requestedWidth": 1440, - "viewport": 1440, - "layoutWidth": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 34, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true + "path": "/api/v1/simulations/dfe148c6-4cca-42ec-9ff3-b2128e300876", + "status": 200, + "qaTarget": "investmoz_test:55441" } - ], - "passed": true - }, - { - "state": "result", - "viewports": [ + ], + "simulationResponseShapes": [ { - "requestedWidth": 320, - "viewport": 320, - "layoutWidth": 320, - "documentScroll": 320, - "bodyScroll": 320, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 385, - "width": 348, - "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" + "httpStatus": 202, + "outerKeys": ["access_token", "data", "replayed"], + "replayedType": "boolean", + "replayed": false, + "accessTokenLength": 64, + "data": { + "keys": [ + "anonymous_token_hash", + "calculation_snapshot", + "created_at", + "id", + "input_snapshot", + "organization_id", + "owner_user_id", + "questionnaire_version_id", + "reports", + "scenarios", + "status", + "updated_at", + "version_no" + ], + "idType": "string", + "status": "queued", + "versionNoType": "number", + "resultSnapshotType": "undefined" } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 360, - "viewport": 360, - "layoutWidth": 360, - "documentScroll": 360, - "bodyScroll": 360, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [ - { - "tag": "OL", - "id": null, - "className": "flex min-w-max gap-2", - "left": 37, - "right": 385, - "width": 348, - "text": "1. Finalidade\n2. Operação fiscal\n3. Revisão" - }, - { - "tag": "LI", - "id": null, - "className": "", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - }, - { - "tag": "BUTTON", - "id": null, - "className": "rounded-full border px-3 py-2 text-sm focus-visible:outline-2 bg-white", - "left": 296, - "right": 385, - "width": 89, - "text": "3. Revisão" - } - ], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 390, - "viewport": 390, - "layoutWidth": 390, - "documentScroll": 390, - "bodyScroll": 390, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 768, - "viewport": 768, - "layoutWidth": 753, - "documentScroll": 753, - "bodyScroll": 753, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 1024, - "viewport": 1024, - "layoutWidth": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true - }, - { - "requestedWidth": 1440, - "viewport": 1440, - "layoutWidth": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "unnamedControls": [], - "unnamedButtons": 0, - "unnamedLinks": 0, - "visibleControls": 37, - "overflowElements": [], - "unnamedAxControls": [], - "passed": true } - ], - "passed": true - } - ], - "expectedArithmeticValue": "50", - "title": "Simulador Fiscal - Laravel", - "resultText": [ - "IRPS", - "INSS", - "IVA", - "IRPC", - "ISPC", - "Retenções", - "50.000000000000000000000000000000000000" - ], - "completed": true, - "taxDisclaimer": true, - "visibleAlerts": [], - "anonymousIdentityNotice": true, - "calculationRequests": [ - { - "path": "/api/v1/simulations", - "status": 202, - "qaTarget": "investmoz_test:55441" - } - ], - "apiResponses": [ - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_test:55441" - }, - { - "path": "/api/v1/simulation-drafts/current", - "status": 200, - "qaTarget": "investmoz_test:55441" - }, - { - "path": "/api/v1/simulation-drafts", - "status": 201, - "qaTarget": "investmoz_test:55441" - }, - { - "path": "/api/v1/simulations", - "status": 202, - "qaTarget": "investmoz_test:55441" - }, - { - "path": "/api/v1/simulation-drafts/8286b973-eecb-44ac-8579-2413f5ade5bc", - "status": 200, - "qaTarget": "investmoz_test:55441" - }, - { - "path": "/api/v1/simulations/dfe148c6-4cca-42ec-9ff3-b2128e300876", - "status": 200, - "qaTarget": "investmoz_test:55441" - } - ], - "simulationResponseShapes": [ - { - "httpStatus": 202, - "outerKeys": [ - "access_token", - "data", - "replayed" - ], - "replayedType": "boolean", - "replayed": false, - "accessTokenLength": 64, - "data": { - "keys": [ - "anonymous_token_hash", - "calculation_snapshot", - "created_at", - "id", - "input_snapshot", - "organization_id", - "owner_user_id", - "questionnaire_version_id", - "reports", - "scenarios", - "status", - "updated_at", - "version_no" - ], - "idType": "string", - "status": "queued", - "versionNoType": "number", - "resultSnapshotType": "undefined" - } - } - ], - "failedResponses": [], - "javascriptExceptions": [], - "passed": true + ], + "failedResponses": [], + "javascriptExceptions": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-cashflow-browser-2026-10-03.json b/docs/investmoz/simulations/simulator-cashflow-browser-2026-10-03.json index 067f061b..475ea7df 100644 --- a/docs/investmoz/simulations/simulator-cashflow-browser-2026-10-03.json +++ b/docs/investmoz/simulations/simulator-cashflow-browser-2026-10-03.json @@ -1,89 +1,89 @@ { - "checkedAt": "2026-10-03T15:36:06.727Z", - "route": "http://127.0.0.1:18117/tools/simulator-cashflow-preview.html", - "browser": "Microsoft Edge headless via CDP", - "scope": "React cashflow component only; no Laravel, API, database, or fiscal rules", - "fixedMode": { - "selected": true, - "monthlyInputs": 1, - "incompleteNote": "Há pelo menos um mês sem pressuposto de imposto: o saldo total desde esse ponto é indeterminado. A caixa conhecida inclui os impostos declarados e omite apenas os meses em falta; mês vazio não significa imposto zero." - }, - "partialSchedule": { - "firstMonthTax": "250.00", - "secondMonthTax": "500.00", - "thirdMonthTax": "Indeterminado", - "incompleteNote": "Há pelo menos um mês sem pressuposto de imposto: o saldo total desde esse ponto é indeterminado. A caixa conhecida inclui os impostos declarados e omite apenas os meses em falta; mês vazio não significa imposto zero.", - "incompleteSummary": true - }, - "completeSchedule": { - "hasCompleteBalance": true, - "zeroTaxMonths": true, - "rowCount": 12 - }, - "responsive": [ - { - "requested": 320, - "viewport": 320, - "documentWidth": 320, - "documentScroll": 320, - "bodyScroll": 320, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 + "checkedAt": "2026-10-03T15:36:06.727Z", + "route": "http://127.0.0.1:18117/tools/simulator-cashflow-preview.html", + "browser": "Microsoft Edge headless via CDP", + "scope": "React cashflow component only; no Laravel, API, database, or fiscal rules", + "fixedMode": { + "selected": true, + "monthlyInputs": 1, + "incompleteNote": "Há pelo menos um mês sem pressuposto de imposto: o saldo total desde esse ponto é indeterminado. A caixa conhecida inclui os impostos declarados e omite apenas os meses em falta; mês vazio não significa imposto zero." }, - { - "requested": 360, - "viewport": 360, - "documentWidth": 360, - "documentScroll": 360, - "bodyScroll": 360, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 + "partialSchedule": { + "firstMonthTax": "250.00", + "secondMonthTax": "500.00", + "thirdMonthTax": "Indeterminado", + "incompleteNote": "Há pelo menos um mês sem pressuposto de imposto: o saldo total desde esse ponto é indeterminado. A caixa conhecida inclui os impostos declarados e omite apenas os meses em falta; mês vazio não significa imposto zero.", + "incompleteSummary": true }, - { - "requested": 390, - "viewport": 390, - "documentWidth": 390, - "documentScroll": 390, - "bodyScroll": 390, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 + "completeSchedule": { + "hasCompleteBalance": true, + "zeroTaxMonths": true, + "rowCount": 12 }, - { - "requested": 768, - "viewport": 768, - "documentWidth": 753, - "documentScroll": 753, - "bodyScroll": 753, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 - }, - { - "requested": 1024, - "viewport": 1024, - "documentWidth": 1009, - "documentScroll": 1009, - "bodyScroll": 1009, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 - }, - { - "requested": 1440, - "viewport": 1440, - "documentWidth": 1425, - "documentScroll": 1425, - "bodyScroll": 1425, - "hasHorizontalOverflow": false, - "inputs": 24, - "unnamedInputs": 0 - } - ], - "unnamedAxControls": [], - "javascriptExceptions": [], - "networkFailures": [], - "passed": true + "responsive": [ + { + "requested": 320, + "viewport": 320, + "documentWidth": 320, + "documentScroll": 320, + "bodyScroll": 320, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + }, + { + "requested": 360, + "viewport": 360, + "documentWidth": 360, + "documentScroll": 360, + "bodyScroll": 360, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + }, + { + "requested": 390, + "viewport": 390, + "documentWidth": 390, + "documentScroll": 390, + "bodyScroll": 390, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + }, + { + "requested": 768, + "viewport": 768, + "documentWidth": 753, + "documentScroll": 753, + "bodyScroll": 753, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + }, + { + "requested": 1024, + "viewport": 1024, + "documentWidth": 1009, + "documentScroll": 1009, + "bodyScroll": 1009, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + }, + { + "requested": 1440, + "viewport": 1440, + "documentWidth": 1425, + "documentScroll": 1425, + "bodyScroll": 1425, + "hasHorizontalOverflow": false, + "inputs": 24, + "unnamedInputs": 0 + } + ], + "unnamedAxControls": [], + "javascriptExceptions": [], + "networkFailures": [], + "passed": true } diff --git a/docs/investmoz/simulations/simulator-continuity-browser-2026-10-04.json b/docs/investmoz/simulations/simulator-continuity-browser-2026-10-04.json index 2892a963..1380b82e 100644 --- a/docs/investmoz/simulations/simulator-continuity-browser-2026-10-04.json +++ b/docs/investmoz/simulations/simulator-continuity-browser-2026-10-04.json @@ -1,21 +1,21 @@ { - "date": "2026-10-04", - "scenario": "anonimo_guarda_fecha_reabre_recupera", - "browser": "Chromium via Playwright", - "viewport_css_px": { "width": 390, "height": 900 }, - "persistent_profile": true, - "browser_context_closed_and_relaunched": true, - "saved": true, - "recovered_title_matches": true, - "save_message": "Rascunho guardado. Pode continuar neste navegador até 3 de novembro de 2026.", - "recovery_message": "Rascunho recuperado. Guardado neste navegador até 3 de novembro de 2026.", - "recovery_cookie": { - "name": "investmoz_simulation_recovery", - "http_only": true, - "same_site": "Lax", - "path": "/api/v1", - "expiry": "2026-11-03T11:40:01.822Z" - }, - "page_errors": [], - "scope": "Ensaio técnico sintético num perfil Chromium persistente; não é sessão com voluntário nem teste num telemóvel físico." + "date": "2026-10-04", + "scenario": "anonimo_guarda_fecha_reabre_recupera", + "browser": "Chromium via Playwright", + "viewport_css_px": { "width": 390, "height": 900 }, + "persistent_profile": true, + "browser_context_closed_and_relaunched": true, + "saved": true, + "recovered_title_matches": true, + "save_message": "Rascunho guardado. Pode continuar neste navegador até 3 de novembro de 2026.", + "recovery_message": "Rascunho recuperado. Guardado neste navegador até 3 de novembro de 2026.", + "recovery_cookie": { + "name": "investmoz_simulation_recovery", + "http_only": true, + "same_site": "Lax", + "path": "/api/v1", + "expiry": "2026-11-03T11:40:01.822Z" + }, + "page_errors": [], + "scope": "Ensaio técnico sintético num perfil Chromium persistente; não é sessão com voluntário nem teste num telemóvel físico." } diff --git a/docs/investmoz/simulations/simulator-final-validation-browser-2026-10-05.json b/docs/investmoz/simulations/simulator-final-validation-browser-2026-10-05.json index 533523fa..5d8510c4 100644 --- a/docs/investmoz/simulations/simulator-final-validation-browser-2026-10-05.json +++ b/docs/investmoz/simulations/simulator-final-validation-browser-2026-10-05.json @@ -1,47 +1,96 @@ { - "date": "2026-10-05", - "purpose": "Isolated functional validation of the InvestMoz simulator dossier", - "database": { - "engine": "PostgreSQL 17.11 with PostGIS", - "host": "127.0.0.1", - "port": 55441, - "name": "investmoz_test", - "guard": "tests/TestCase.php rejects active DB name investemoz, any DB name without _test suffix, non-loopback host, or DB_URL; verified rejection occurs before connection", - "backup": "%TEMP%\\investmoz_test-before-validation-20261005.dump", - "active_service_5432_untouched": true - }, - "server": { - "url": "http://127.0.0.1:18089/Simulador", - "binding": "127.0.0.1 only", - "qa_target_header": "investmoz_test:55441", - "isolated_router": "tools/simulator-qa-router.php", - "current_compiled_assets": true - }, - "tests": { - "php_targeted": {"command": "php artisan test --filter=SimulationFormDraftTest", "passed": 13, "assertions": 98}, - "php_full": {"command": "php artisan test", "passed": 278, "assertions": 2901}, - "frontend": {"command": "node --test tests/frontend/*.test.mjs", "passed": 144, "failed": 0}, - "types": {"command": "npm.cmd run types:check", "result": "passed"}, - "syntax": {"command": "php -l tests/TestCase.php; php -l tools/simulator-qa-router.php", "result": "both passed"} - }, - "browser": { - "entrepreneur": {"sections": 6, "saved": true, "recovered_after_fresh_browser_context": true, "manual_response_survives_second_restart": true, "manual_response_edit_survives_restart": true, "risk_response_owner_cost_due_date_persisted": true, "finance_values_match_existing_forecast": {"revenue_mzn": "60000.00", "profit_before_tax_mzn": "30000.00", "closing_cash_mzn": "4000.00"}, "exported_docx": true, "exported_attachment_zip": true}, - "legacy_draft": {"payload_without_questionnaire_version": "created and reopened successfully", "version": "legacy-unversioned"}, - "investor": {"sections": 8, "review_readiness_visible": true, "docx_download": true}, - "http_statuses": "draft GET 200, create POST 201, update PUT 200; QA target header verified on requests", - "javascript_errors": 0, - "screenshots": { - "entrepreneur_mobile": "%TEMP%\\investmoz-dossier-validation\\entrepreneur-review-mobile-current-build.png", - "investor_desktop": "%TEMP%\\investmoz-dossier-validation\\investor-review-desktop-current-build.png" - } - }, - "exports": { - "entrepreneur_docx": "%TEMP%\\investmoz-dossier-validation\\portal-entrepreneur-current.docx", - "investor_docx": "%TEMP%\\investmoz-dossier-validation\\portal-investor-current.docx", - "attachments_zip": "%TEMP%\\investmoz-dossier-validation\\portal-entrepreneur-current.zip", - "docx_openxml_inspection": {"content_types_and_document_xml": true, "table": true, "photo_media_and_relationship": true, "photo_caption": "Synthetic project site photo", "manual_response": true, "forecast_values_match": true, "pdf_nonembedding_explanation": true, "page_number_field": true, "zip_contains_original_pdf_csv_png_and_index": true}, - "visual_pagination": {"status": "pending", "reason": "Microsoft Word automation did not open reliably; no LibreOffice/soffice, Pandoc, or PDF rendering utilities are installed. No visual pagination claim is made."} - }, - "known_qa_issue_and_resolution": "A stale shared Vite dev server was serving an outdated JavaScript bundle. The QA router now forces the current compiled assets via an isolated hot-file path; current browser runs used the corrected bundle.", - "artifacts_directory": "%TEMP%\\investmoz-dossier-validation" + "date": "2026-10-05", + "purpose": "Isolated functional validation of the InvestMoz simulator dossier", + "database": { + "engine": "PostgreSQL 17.11 with PostGIS", + "host": "127.0.0.1", + "port": 55441, + "name": "investmoz_test", + "guard": "tests/TestCase.php rejects active DB name investemoz, any DB name without _test suffix, non-loopback host, or DB_URL; verified rejection occurs before connection", + "backup": "%TEMP%\\investmoz_test-before-validation-20261005.dump", + "active_service_5432_untouched": true + }, + "server": { + "url": "http://127.0.0.1:18089/Simulador", + "binding": "127.0.0.1 only", + "qa_target_header": "investmoz_test:55441", + "isolated_router": "tools/simulator-qa-router.php", + "current_compiled_assets": true + }, + "tests": { + "php_targeted": { + "command": "php artisan test --filter=SimulationFormDraftTest", + "passed": 13, + "assertions": 98 + }, + "php_full": { + "command": "php artisan test", + "passed": 278, + "assertions": 2901 + }, + "frontend": { + "command": "node --test tests/frontend/*.test.mjs", + "passed": 144, + "failed": 0 + }, + "types": { "command": "npm.cmd run types:check", "result": "passed" }, + "syntax": { + "command": "php -l tests/TestCase.php; php -l tools/simulator-qa-router.php", + "result": "both passed" + } + }, + "browser": { + "entrepreneur": { + "sections": 6, + "saved": true, + "recovered_after_fresh_browser_context": true, + "manual_response_survives_second_restart": true, + "manual_response_edit_survives_restart": true, + "risk_response_owner_cost_due_date_persisted": true, + "finance_values_match_existing_forecast": { + "revenue_mzn": "60000.00", + "profit_before_tax_mzn": "30000.00", + "closing_cash_mzn": "4000.00" + }, + "exported_docx": true, + "exported_attachment_zip": true + }, + "legacy_draft": { + "payload_without_questionnaire_version": "created and reopened successfully", + "version": "legacy-unversioned" + }, + "investor": { + "sections": 8, + "review_readiness_visible": true, + "docx_download": true + }, + "http_statuses": "draft GET 200, create POST 201, update PUT 200; QA target header verified on requests", + "javascript_errors": 0, + "screenshots": { + "entrepreneur_mobile": "%TEMP%\\investmoz-dossier-validation\\entrepreneur-review-mobile-current-build.png", + "investor_desktop": "%TEMP%\\investmoz-dossier-validation\\investor-review-desktop-current-build.png" + } + }, + "exports": { + "entrepreneur_docx": "%TEMP%\\investmoz-dossier-validation\\portal-entrepreneur-current.docx", + "investor_docx": "%TEMP%\\investmoz-dossier-validation\\portal-investor-current.docx", + "attachments_zip": "%TEMP%\\investmoz-dossier-validation\\portal-entrepreneur-current.zip", + "docx_openxml_inspection": { + "content_types_and_document_xml": true, + "table": true, + "photo_media_and_relationship": true, + "photo_caption": "Synthetic project site photo", + "manual_response": true, + "forecast_values_match": true, + "pdf_nonembedding_explanation": true, + "page_number_field": true, + "zip_contains_original_pdf_csv_png_and_index": true + }, + "visual_pagination": { + "status": "pending", + "reason": "Microsoft Word automation did not open reliably; no LibreOffice/soffice, Pandoc, or PDF rendering utilities are installed. No visual pagination claim is made." + } + }, + "known_qa_issue_and_resolution": "A stale shared Vite dev server was serving an outdated JavaScript bundle. The QA router now forces the current compiled assets via an isolated hot-file path; current browser runs used the corrected bundle.", + "artifacts_directory": "%TEMP%\\investmoz-dossier-validation" } diff --git a/docs/investmoz/simulations/simulator-journey-browser-2026-10-04.json b/docs/investmoz/simulations/simulator-journey-browser-2026-10-04.json index aec0254d..f1200ffb 100644 --- a/docs/investmoz/simulations/simulator-journey-browser-2026-10-04.json +++ b/docs/investmoz/simulations/simulator-journey-browser-2026-10-04.json @@ -1,72 +1,72 @@ { - "date": "2026-10-04", - "purpose": "Ensaio visual e funcional do percurso exploratorio existente; sem introducao de funcionalidades", - "environment": { - "browser": "Chromium via Playwright", - "application": "Laravel servido localmente com PostgreSQL 17 isolado para QA", - "viewports_px": [390, 768, 1024, 1440], - "physical_mobile_device": false, - "spreadsheet": "Microsoft Excel via automacao COM local" - }, - "scenarios": [ - { - "name": "ideia simples", - "result": "passou", - "save_leave_recover": "Rascunho enviado ao servidor (201), navegacao para outra pagina e recuperacao do titulo ao regressar (200)", - "validation": "Campo obrigatorio vazio mostrou mensagem acessivel e foco no primeiro campo invalido", - "market_origin": "Declarada, identificada como nao verificada; origem iniciada por '=' foi sanitizada no CSV", - "calculations": { - "loan_net_disbursed_mzn": "100000.00", - "loan_rate_per_period_percent": "1.500000", - "loan_interest_mzn": "19817.83", - "loan_total_paid_mzn": "119817.83", - "first_payment_mzn": "4992.41", - "break_even_units": 125, - "break_even_sales_mzn": "125000.00", - "funding_gap_mzn": "750000.00", - "operating_balance_mzn": "50000.00" - }, - "csv": "UTF-8 BOM, acentos e valores conferidos; formula neutralizada; Excel abriu 30 linhas e 4 colunas sem dividir campos" + "date": "2026-10-04", + "purpose": "Ensaio visual e funcional do percurso exploratorio existente; sem introducao de funcionalidades", + "environment": { + "browser": "Chromium via Playwright", + "application": "Laravel servido localmente com PostgreSQL 17 isolado para QA", + "viewports_px": [390, 768, 1024, 1440], + "physical_mobile_device": false, + "spreadsheet": "Microsoft Excel via automacao COM local" }, - { - "name": "negocio existente (maturidade Operacao)", - "result": "passou apos correcao", - "save_leave_recover": "Titulo e maturidade recuperados do rascunho do servidor", - "finding": "A API rejeitava o valor de maturidade Operacao por falta na whitelist dos rotulos de seleccao; corrigida em StoreSimulationFormDraftRequest" + "scenarios": [ + { + "name": "ideia simples", + "result": "passou", + "save_leave_recover": "Rascunho enviado ao servidor (201), navegacao para outra pagina e recuperacao do titulo ao regressar (200)", + "validation": "Campo obrigatorio vazio mostrou mensagem acessivel e foco no primeiro campo invalido", + "market_origin": "Declarada, identificada como nao verificada; origem iniciada por '=' foi sanitizada no CSV", + "calculations": { + "loan_net_disbursed_mzn": "100000.00", + "loan_rate_per_period_percent": "1.500000", + "loan_interest_mzn": "19817.83", + "loan_total_paid_mzn": "119817.83", + "first_payment_mzn": "4992.41", + "break_even_units": 125, + "break_even_sales_mzn": "125000.00", + "funding_gap_mzn": "750000.00", + "operating_balance_mzn": "50000.00" + }, + "csv": "UTF-8 BOM, acentos e valores conferidos; formula neutralizada; Excel abriu 30 linhas e 4 colunas sem dividir campos" + }, + { + "name": "negocio existente (maturidade Operacao)", + "result": "passou apos correcao", + "save_leave_recover": "Titulo e maturidade recuperados do rascunho do servidor", + "finding": "A API rejeitava o valor de maturidade Operacao por falta na whitelist dos rotulos de seleccao; corrigida em StoreSimulationFormDraftRequest" + }, + { + "name": "informacao incompleta", + "result": "passou", + "validation": "Campos obrigatorios continuam validados; localizacao e detalhes opcionais ausentes nao bloquearam o percurso exploratorio" + } + ], + "visual_and_accessibility": { + "horizontal_overflow": false, + "keyboard_focus_visible": true, + "label_font_px": 14, + "help_text_min_font_px": 12, + "screenshots": "Capturas temporarias em viewport 390 px revistas visualmente; sem overflow; formularios e mensagens legiveis" }, - { - "name": "informacao incompleta", - "result": "passou", - "validation": "Campos obrigatorios continuam validados; localizacao e detalhes opcionais ausentes nao bloquearam o percurso exploratorio" - } - ], - "visual_and_accessibility": { - "horizontal_overflow": false, - "keyboard_focus_visible": true, - "label_font_px": 14, - "help_text_min_font_px": 12, - "screenshots": "Capturas temporarias em viewport 390 px revistas visualmente; sem overflow; formularios e mensagens legiveis" - }, - "browser_errors": { "console": 0, "page": 0 }, - "api_responses": { "draft_create": 201, "draft_read": 200 }, - "fixes": [ - "Incluido maturity_stage na whitelist de rotulos validos do rascunho.", - "Exportacao do dossier passou a citar delimitadores regionais de CSV sem alterar o comportamento do helper existente.", - "CSV do dossier passou a expor pressupostos e resultados financeiros em campos legiveis, em vez de um bloco JSON opaco.", - "Corrigida codificacao de textos visiveis que apresentavam caracteres corrompidos durante o percurso." - ], - "automated_checks": { - "frontend_node_test": "129/129 passou", - "typescript_types_check": "passou", - "vite_build": "passou (2399 modulos; aviso opcional: fontaine ausente)", - "phpstan": "0 erros", - "simulation_form_phpunit": "12 testes, 91 assertions, passou", - "git_diff_check": "passou" - }, - "limits": [ - "Dispositivo fisico nao testado; viewport mobile foi emulado em Chromium.", - "Sair e recuperar foi testado navegando para outra pagina e regressando na mesma sessao do browser; encerrar o browser por completo nao foi ensaiado.", - "O caso negocio existente cobre a maturidade Operacao no simulador exploratorio, nao o fluxo autenticado de CRUD do marketplace de negocios existentes.", - "Excel foi automatizado localmente e confirmou delimitacao, acentos e valores; nao foram ensaiadas outras versoes ou configuracoes regionais de folha de calculo." - ] + "browser_errors": { "console": 0, "page": 0 }, + "api_responses": { "draft_create": 201, "draft_read": 200 }, + "fixes": [ + "Incluido maturity_stage na whitelist de rotulos validos do rascunho.", + "Exportacao do dossier passou a citar delimitadores regionais de CSV sem alterar o comportamento do helper existente.", + "CSV do dossier passou a expor pressupostos e resultados financeiros em campos legiveis, em vez de um bloco JSON opaco.", + "Corrigida codificacao de textos visiveis que apresentavam caracteres corrompidos durante o percurso." + ], + "automated_checks": { + "frontend_node_test": "129/129 passou", + "typescript_types_check": "passou", + "vite_build": "passou (2399 modulos; aviso opcional: fontaine ausente)", + "phpstan": "0 erros", + "simulation_form_phpunit": "12 testes, 91 assertions, passou", + "git_diff_check": "passou" + }, + "limits": [ + "Dispositivo fisico nao testado; viewport mobile foi emulado em Chromium.", + "Sair e recuperar foi testado navegando para outra pagina e regressando na mesma sessao do browser; encerrar o browser por completo nao foi ensaiado.", + "O caso negocio existente cobre a maturidade Operacao no simulador exploratorio, nao o fluxo autenticado de CRUD do marketplace de negocios existentes.", + "Excel foi automatizado localmente e confirmou delimitacao, acentos e valores; nao foram ensaiadas outras versoes ou configuracoes regionais de folha de calculo." + ] } diff --git a/docs/investmoz/simulations/stage-10-extension-traceability.md b/docs/investmoz/simulations/stage-10-extension-traceability.md index 92f5dfea..eeae3f28 100644 --- a/docs/investmoz/simulations/stage-10-extension-traceability.md +++ b/docs/investmoz/simulations/stage-10-extension-traceability.md @@ -3,16 +3,16 @@ **Data:** 2026-10-03 **Estado proposto:** funcionalidades informativas/territoriais parciais; incentivos fiscais, aduaneiro, câmbio e decisões regulatórias permanecem inactivos. -| Requisito do anexo | Evidência actual | Estado e limite | -|---|---|---| -| Distinguir informação territorial de benefício ou autorização | `resources/js/pages/investment/zone.tsx` informa expressamente que a ficha e a associação territorial não atribuem benefício, licença ou autorização aduaneira. | Implementado na interface da zona; não cobre todas as páginas de sectores/licenciamento. | -| Publicar apenas registos revistos e com fonte vigente | `InvestmentZones::visible()` exige estado publicado/validado, fonte jurídica validada e vigente e operador não eliminado. | Implementado; não confirma por si a validade material da interpretação nem a autenticidade do PDF/hash da fonte. | -| Filtrar relações e mapas por validação/vigência | `InvestmentZones::links()` filtra relação validada, período e fonte quando atribuída; `InvestmentZoneGeometryController::map()` exige publicação autorizada, geometria validada e classificação pública. | Implementado com limites. Relações sem fonte podem ser públicas se validadas; isso é apropriado só para relações factuais não normativas e deve ser revisto por tipo. | -| Ligar zonas a oportunidades sem conceder elegibilidade | `InvestmentZoneController` apresenta oportunidades publicadas associadas; `InvestmentSubmissionController` valida a relação e guarda versão/origem da zona. | Contexto de descoberta/submissão; associação não atribui incentivo, benefício ou elegibilidade do investidor. | -| Rever relações e geometrias com segregação | `InvestmentZoneRelationController`, `InvestmentZoneGeometryController` e `InvestmentZoneLinkController` têm validação independente, versão, transacção e auditoria; os testes verificam recusa de auto-revisão e bloqueio de publicação sem aprovação. | Coberto tecnicamente no fluxo territorial existente. | -| Incentivos fiscais e aduaneiros | Não foi encontrada avaliação de regime/certificado/período/condições que conceda benefício, nem cálculo pautal/fonte aduaneira executável. A UI fiscal mantém ausência como indeterminação. | Não implementado/publicável; dependente de texto oficial, interpretação, parâmetros, fontes e casos aprovados. | -| Câmbio, vistos, residência, trabalho, concessões e licenciamento | Existem páginas/fontes educacionais e cadastros territoriais relacionados, mas nesta revisão não foi demonstrado um fluxo completo de obrigação, elegibilidade ou encaminhamento validado para cada domínio. | Parcial/a validar por domínio; não os tratar como decisão oficial. | -| Cartografia/educação como contexto | Há catálogos geográficos e módulos educacionais; a ligação com cada extensão e a validade temporal do conteúdo exigem revisão individual. | Parcial. Nenhuma recomendação ou qualificação foi inferida nesta tranche. | +| Requisito do anexo | Evidência actual | Estado e limite | +| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Distinguir informação territorial de benefício ou autorização | `resources/js/pages/investment/zone.tsx` informa expressamente que a ficha e a associação territorial não atribuem benefício, licença ou autorização aduaneira. | Implementado na interface da zona; não cobre todas as páginas de sectores/licenciamento. | +| Publicar apenas registos revistos e com fonte vigente | `InvestmentZones::visible()` exige estado publicado/validado, fonte jurídica validada e vigente e operador não eliminado. | Implementado; não confirma por si a validade material da interpretação nem a autenticidade do PDF/hash da fonte. | +| Filtrar relações e mapas por validação/vigência | `InvestmentZones::links()` filtra relação validada, período e fonte quando atribuída; `InvestmentZoneGeometryController::map()` exige publicação autorizada, geometria validada e classificação pública. | Implementado com limites. Relações sem fonte podem ser públicas se validadas; isso é apropriado só para relações factuais não normativas e deve ser revisto por tipo. | +| Ligar zonas a oportunidades sem conceder elegibilidade | `InvestmentZoneController` apresenta oportunidades publicadas associadas; `InvestmentSubmissionController` valida a relação e guarda versão/origem da zona. | Contexto de descoberta/submissão; associação não atribui incentivo, benefício ou elegibilidade do investidor. | +| Rever relações e geometrias com segregação | `InvestmentZoneRelationController`, `InvestmentZoneGeometryController` e `InvestmentZoneLinkController` têm validação independente, versão, transacção e auditoria; os testes verificam recusa de auto-revisão e bloqueio de publicação sem aprovação. | Coberto tecnicamente no fluxo territorial existente. | +| Incentivos fiscais e aduaneiros | Não foi encontrada avaliação de regime/certificado/período/condições que conceda benefício, nem cálculo pautal/fonte aduaneira executável. A UI fiscal mantém ausência como indeterminação. | Não implementado/publicável; dependente de texto oficial, interpretação, parâmetros, fontes e casos aprovados. | +| Câmbio, vistos, residência, trabalho, concessões e licenciamento | Existem páginas/fontes educacionais e cadastros territoriais relacionados, mas nesta revisão não foi demonstrado um fluxo completo de obrigação, elegibilidade ou encaminhamento validado para cada domínio. | Parcial/a validar por domínio; não os tratar como decisão oficial. | +| Cartografia/educação como contexto | Há catálogos geográficos e módulos educacionais; a ligação com cada extensão e a validade temporal do conteúdo exigem revisão individual. | Parcial. Nenhuma recomendação ou qualificação foi inferida nesta tranche. | ## Verificação executada @@ -28,4 +28,4 @@ 3. Fazer inventário por domínio de vistos, trabalho, câmbio, concessões e licenciamento, ligando cada orientação apenas a fonte e vigência verificadas. 4. Ensaiar no browser a compreensão do aviso de que mapa/zona não confere elegibilidade, licença ou benefício. -Rollback: remover este documento e as adendas correspondentes no estado/ledger. Sem rollback de schema ou dados. \ No newline at end of file +Rollback: remover este documento e as adendas correspondentes no estado/ledger. Sem rollback de schema ou dados. diff --git a/docs/investmoz/simulations/stage-10-gate.md b/docs/investmoz/simulations/stage-10-gate.md index 6c0cd73d..f816ef75 100644 --- a/docs/investmoz/simulations/stage-10-gate.md +++ b/docs/investmoz/simulations/stage-10-gate.md @@ -6,16 +6,16 @@ ## Inspecção e conformidade -| Critério | Evidência do repositório | Estado | -| --- | --- | --- | -| Laravel / frontend / dados | Laravel 13.30.1, PHP CLI 8.5.11, React 19/Inertia, PostgreSQL/PostGIS nas migrations e no CI | CONFORME como stack base | -| Regras funcionais | Fórmulas e matriz de casos foram fornecidas e consideradas formalmente aprovadas pelo utilizador em 2026-10-01. `ADR-004` continua sem parâmetros concretos de produção | PARCIAL — falta catálogo de parâmetros/fontes vigentes | -| Snapshots | `SimulationExecution` guarda snapshots de entrada/cálculo/resultado; o questionário é `user-entered-arithmetic`, sem taxa legal (`legal_rate_source: null`) | PARCIAL — infraestrutura genérica, sem snapshot versionado de regras fiscais | -| API estabilizada para os cálculos do MVP | API aceita `base`, `rate` e `reduction` para aritmética genérica; não cobre IRPC, IVA, ISPC, IRPS, INSS ou retenções como regras versionadas. A última tentativa PHPUnit não passou das asserções | BLOQUEADO | -| DSL whitelist e DAG sem ciclos | `ADR-004` descreve uma futura AST tipada como restrição; não há avaliador, validador de DAG ou testes de ciclos no código inspeccionado | AUSENTE | -| Decimal e arredondamento configurado | `ADR-006` exige precisão decimal, mas deixa escala, modo e momento de arredondamento dependentes de aprovação do domínio | BLOQUEADO | -| Casos dourados e fontes | Matriz de casos esperados fornecida e aprovada funcionalmente pelo utilizador; não há fontes legais oficiais associadas nem assinaturas nominais de Fiscalista/QA | PARCIAL — fontes e evidência de revisão pendentes | -| RLS e operação segura | `ADR-007` mantém a matriz IAM/RLS pendente; a eficácia de policies/grants não foi demonstrada nesta execução | BLOQUEADO | +| Critério | Evidência do repositório | Estado | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| Laravel / frontend / dados | Laravel 13.30.1, PHP CLI 8.5.11, React 19/Inertia, PostgreSQL/PostGIS nas migrations e no CI | CONFORME como stack base | +| Regras funcionais | Fórmulas e matriz de casos foram fornecidas e consideradas formalmente aprovadas pelo utilizador em 2026-10-01. `ADR-004` continua sem parâmetros concretos de produção | PARCIAL — falta catálogo de parâmetros/fontes vigentes | +| Snapshots | `SimulationExecution` guarda snapshots de entrada/cálculo/resultado; o questionário é `user-entered-arithmetic`, sem taxa legal (`legal_rate_source: null`) | PARCIAL — infraestrutura genérica, sem snapshot versionado de regras fiscais | +| API estabilizada para os cálculos do MVP | API aceita `base`, `rate` e `reduction` para aritmética genérica; não cobre IRPC, IVA, ISPC, IRPS, INSS ou retenções como regras versionadas. A última tentativa PHPUnit não passou das asserções | BLOQUEADO | +| DSL whitelist e DAG sem ciclos | `ADR-004` descreve uma futura AST tipada como restrição; não há avaliador, validador de DAG ou testes de ciclos no código inspeccionado | AUSENTE | +| Decimal e arredondamento configurado | `ADR-006` exige precisão decimal, mas deixa escala, modo e momento de arredondamento dependentes de aprovação do domínio | BLOQUEADO | +| Casos dourados e fontes | Matriz de casos esperados fornecida e aprovada funcionalmente pelo utilizador; não há fontes legais oficiais associadas nem assinaturas nominais de Fiscalista/QA | PARCIAL — fontes e evidência de revisão pendentes | +| RLS e operação segura | `ADR-007` mantém a matriz IAM/RLS pendente; a eficácia de policies/grants não foi demonstrada nesta execução | BLOQUEADO | ## Divergência de contrato a resolver @@ -134,6 +134,7 @@ Ficheiros identificados pela estrutura actual para uma futura alteração: criar - A governação valida esta DSL antes de guardar rascunhos. O avaliador não está ligado ao endpoint público. Valores ausentes resultam em `indeterminate`; nenhum default fiscal é introduzido. - Teste sintético de base 1234.56 e factor 0.16 verifica somente a mecânica, não representa taxa nem regra legal. - **Estado permanece BLOQUEADO para cálculo/publicação fiscal.** Fonte, vigência, snapshot, explicabilidade legal e casos fiscais dourados continuam sem integração/aprovação verificável. + ### Parâmetros versionados e modo de planeamento (2026-10-03) Foi acrescentado um resolvedor interno de candidatos já autorizados: exige publicação, correspondência exacta de componente e `scope_key`, vigência na data pedida, fonte validada na mesma versão e condições. Sem candidato ou perante sobreposição retorna indeterminação. DSL `investmoz.tax.v1` agora valida a forma dos parâmetros `value`, `unit` e `conditions`. Para rever valores sem mudar a fórmula, cria-se uma nova versão governada com a mesma AST e novos parâmetros; nenhuma versão publicada é alterada. @@ -141,6 +142,7 @@ Foi acrescentado um resolvedor interno de candidatos já autorizados: exige publ O formulário distingue escolha automática de parâmetros aprovados, planeamento personalizado e ausência de cálculo. Apenas planeamento personalizado solicita valores hipotéticos. É possível guardar o rascunho sem os preencher e continuar o percurso; se iniciar o cenário, os três valores devem ser completos e válidos. O resultado genérico traz `scenario_kind=custom_planning` nos snapshots. A escolha automática ainda não resolve parâmetros na página nem mostra valor/fonte porque RLS só permite consultas de governação, e o avaliador continua desligado do job público. Não abrir acesso à tabela completa: falta endpoint público que projecta apenas parâmetros/fonte seguros e integração de snapshot fiscal. Logo o gate permanece **BLOQUEADO** para cálculo/publicação fiscal. + ## Reconciliação de estado — 2026-10-03 -A secção inicial deste documento é histórica e ficou desactualizada: já existem governação versionada, resolvedor de parâmetros publicados e o avaliador DSL `investmoz.tax.v1` com DAG/whitelist. A verificação e as lacunas actuais estão em `stage-10-technical-traceability.md`. Estado corrigido: **infraestrutura técnica parcial; cálculo fiscal legal/publicação continua bloqueado**. A nova evidência de testes é 9/9 (27 asserções) para DSL/resolvedor e 4/4 (52 asserções) para governação em PostgreSQL QA isolado. Nenhuma taxa/regra real foi activada; não há integração do avaliador com `RunSimulation` nem casos fiscais golden aprovados por Fiscalista. \ No newline at end of file +A secção inicial deste documento é histórica e ficou desactualizada: já existem governação versionada, resolvedor de parâmetros publicados e o avaliador DSL `investmoz.tax.v1` com DAG/whitelist. A verificação e as lacunas actuais estão em `stage-10-technical-traceability.md`. Estado corrigido: **infraestrutura técnica parcial; cálculo fiscal legal/publicação continua bloqueado**. A nova evidência de testes é 9/9 (27 asserções) para DSL/resolvedor e 4/4 (52 asserções) para governação em PostgreSQL QA isolado. Nenhuma taxa/regra real foi activada; não há integração do avaliador com `RunSimulation` nem casos fiscais golden aprovados por Fiscalista. diff --git a/docs/investmoz/simulations/stage-10-technical-traceability.md b/docs/investmoz/simulations/stage-10-technical-traceability.md index dd57b2e1..ca19a37b 100644 --- a/docs/investmoz/simulations/stage-10-technical-traceability.md +++ b/docs/investmoz/simulations/stage-10-technical-traceability.md @@ -7,17 +7,17 @@ O gate histórico `stage-10-gate.md` descrevia correctamente o estado inicial, mas ficou desactualizado após a implementação de governação, resolução de parâmetros e DSL. Hoje existe um avaliador declarativo `investmoz.tax.v1` com operações allowlist e validação de DAG, aritmética `BigRational`, arredondamento explícito, indeterminação de entradas ausentes, resolução por vigência/scope/contexto e fluxo de revisão/publicação de versões. Isso não significa que o motor fiscal esteja completo ou ligado ao cálculo público. -| Critério | Evidência actual | Estado | -|---|---|---| -| DSL restrita e DAG sem ciclos | `app/Services/SimulationTaxRuleExpression.php`; `SimulationTaxRuleExpressionTest` rejeita operação desconhecida, campos extra e ciclo. | Implementado e testado como avaliador genérico. | -| Aritmética exacta e arredondamento explícito | `Brick\Math\BigRational`; modo/escala só no nó `round`; teste verifica produto decimal e HALF_UP. | Implementado tecnicamente; política fiscal de escala/momento continua sem aprovação por componente. | -| Ausência de parâmetro sem fallback | O avaliador retorna `indeterminate`; `SimulationTaxParameterResolver` cobre ausência, conflito, scope/contexto e mudança dentro do período. | Implementado/testado em unidades; não prova cálculo legal. | -| Parâmetro publicado com fonte vigente | `resolvePublishedForPeriod` filtra versão publicada, fonte validada/versão coerente e vigência; endpoint/UI projectam metadados de fonte. | Infraestrutura presente; exemplar oficial/hash e revisão legal ainda não autenticados para produção. | -| Governação, concorrência e histórico | `SimulationTaxRuleGovernance`, migration `2026_10_03_170000_create_simulation_tax_rule_governance.php` e `SimulationTaxRuleGovernanceTest`: separação de funções, optimistic locking, hash-chain, RLS/eventos imutáveis, revogação e vigências sobrepostas. | Implementado e testado no QA; papéis configurados vazios por defeito e sem regra fiscal real aprovada. | -| Fórmulas completas de IRPC, IVA, ISPC, IRPS, INSS, retenções e importação | A AST e os fixtures existentes demonstram operações genéricas; não foi encontrada integração do avaliador/resolvedor no `RunSimulation` nem matriz golden fiscal executável e ligada a fontes oficiais. | Incompleto. Nenhuma semântica tributária deve ser inferida a partir das operações genéricas. | -| Explicabilidade por linha e snapshot fiscal reproduzível | O avaliador emite passos de nós; não incorpora por si só regra/fonte/artigo/vigência em cada linha nem persiste o resultado dessa execução fiscal no snapshot do cálculo público. | Parcial/incompleto. | -| Aprovação dos casos golden por Fiscalista | Testes sintéticos de DSL/resolvedor/governação; faltam casos fiscais assinados contra texto oficial consolidado e vigência. | Pendente. | -| Segurança da leitura pública | Endpoint de resolução consulta somente parâmetros publicados expostos pela policy específica; governação permanece privada. O motor de execução não chama esse endpoint. | Separação implementada; integração/end-to-end pendente. | +| Critério | Evidência actual | Estado | +| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| DSL restrita e DAG sem ciclos | `app/Services/SimulationTaxRuleExpression.php`; `SimulationTaxRuleExpressionTest` rejeita operação desconhecida, campos extra e ciclo. | Implementado e testado como avaliador genérico. | +| Aritmética exacta e arredondamento explícito | `Brick\Math\BigRational`; modo/escala só no nó `round`; teste verifica produto decimal e HALF_UP. | Implementado tecnicamente; política fiscal de escala/momento continua sem aprovação por componente. | +| Ausência de parâmetro sem fallback | O avaliador retorna `indeterminate`; `SimulationTaxParameterResolver` cobre ausência, conflito, scope/contexto e mudança dentro do período. | Implementado/testado em unidades; não prova cálculo legal. | +| Parâmetro publicado com fonte vigente | `resolvePublishedForPeriod` filtra versão publicada, fonte validada/versão coerente e vigência; endpoint/UI projectam metadados de fonte. | Infraestrutura presente; exemplar oficial/hash e revisão legal ainda não autenticados para produção. | +| Governação, concorrência e histórico | `SimulationTaxRuleGovernance`, migration `2026_10_03_170000_create_simulation_tax_rule_governance.php` e `SimulationTaxRuleGovernanceTest`: separação de funções, optimistic locking, hash-chain, RLS/eventos imutáveis, revogação e vigências sobrepostas. | Implementado e testado no QA; papéis configurados vazios por defeito e sem regra fiscal real aprovada. | +| Fórmulas completas de IRPC, IVA, ISPC, IRPS, INSS, retenções e importação | A AST e os fixtures existentes demonstram operações genéricas; não foi encontrada integração do avaliador/resolvedor no `RunSimulation` nem matriz golden fiscal executável e ligada a fontes oficiais. | Incompleto. Nenhuma semântica tributária deve ser inferida a partir das operações genéricas. | +| Explicabilidade por linha e snapshot fiscal reproduzível | O avaliador emite passos de nós; não incorpora por si só regra/fonte/artigo/vigência em cada linha nem persiste o resultado dessa execução fiscal no snapshot do cálculo público. | Parcial/incompleto. | +| Aprovação dos casos golden por Fiscalista | Testes sintéticos de DSL/resolvedor/governação; faltam casos fiscais assinados contra texto oficial consolidado e vigência. | Pendente. | +| Segurança da leitura pública | Endpoint de resolução consulta somente parâmetros publicados expostos pela policy específica; governação permanece privada. O motor de execução não chama esse endpoint. | Separação implementada; integração/end-to-end pendente. | ## Verificação desta revisão @@ -29,4 +29,4 @@ O gate histórico `stage-10-gate.md` descrevia correctamente o estado inicial, m Actualizar o estado de “avaliador ausente” para **infraestrutura interna parcial**. Manter **bloqueada a execução/publicação de cálculo fiscal legal** até obter fontes oficiais consolidadas e vigentes, interpretação/aprovação competente, parâmetros e arredondamento por âmbito, casos golden aprovados, explicabilidade/fonte no resultado e integração de snapshot/API testada. Não codificar taxas neste incremento. -Rollback desta tranche documental: remover este ficheiro e as adendas que a referenciam; não há alteração de schema/dados a reverter. \ No newline at end of file +Rollback desta tranche documental: remover este ficheiro e as adendas que a referenciam; não há alteração de schema/dados a reverter. diff --git a/docs/investmoz/simulations/stage-11-gate.md b/docs/investmoz/simulations/stage-11-gate.md index 429a1107..e823a04d 100644 --- a/docs/investmoz/simulations/stage-11-gate.md +++ b/docs/investmoz/simulations/stage-11-gate.md @@ -6,13 +6,13 @@ ## Pré-condições e evidência disponível -| Pré-condição | Evidência disponível | Estado | -| --- | --- | --- | -| Motor principal aprovado | O gate da Etapa 10 regista fórmulas e matriz aprovadas funcionalmente pelo utilizador, mas nenhum motor foi implementado nem testado | BLOQUEADO | +| Pré-condição | Evidência disponível | Estado | +| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| Motor principal aprovado | O gate da Etapa 10 regista fórmulas e matriz aprovadas funcionalmente pelo utilizador, mas nenhum motor foi implementado nem testado | BLOQUEADO | | Fontes legais e parâmetros aduaneiros | Foram fornecidas fórmulas e casos sintéticos (`ADU-01` a `ADU-03`, `ICE-01`/`02`, `IMP-IVA-01`); não incluem pauta/catálogo oficial versionado, fontes, vigências ou hashes de parâmetros de produção | BLOQUEADO | -| Câmbio rastreável | `CUR-01` especifica que valor original, taxa, data e fonte devem ser guardados; não define fornecedor/fonte oficial, método nem regra de selecção da taxa | BLOQUEADO | -| Classificação e preferência | A regra fornecida exige código pautal e prova de origem/preferência; não foi fornecido catálogo nem conjunto de documentos/critérios oficiais | BLOQUEADO | -| Ambiente e controlos técnicos | Gate da Etapa 10 ainda não demonstra contrato/API, snapshots fiscais, RLS/IAM nem ambiente isolado de testes | BLOQUEADO | +| Câmbio rastreável | `CUR-01` especifica que valor original, taxa, data e fonte devem ser guardados; não define fornecedor/fonte oficial, método nem regra de selecção da taxa | BLOQUEADO | +| Classificação e preferência | A regra fornecida exige código pautal e prova de origem/preferência; não foi fornecido catálogo nem conjunto de documentos/critérios oficiais | BLOQUEADO | +| Ambiente e controlos técnicos | Gate da Etapa 10 ainda não demonstra contrato/API, snapshots fiscais, RLS/IAM nem ambiente isolado de testes | BLOQUEADO | ## Regras já especificadas funcionalmente @@ -55,6 +55,7 @@ Consultar `docs/investmoz/simulator-evolution-progress.md` para comandos, detalh - Suite PHP `php artisan test`: 251/251 testes, 2.586 asserções, em PostgreSQL 17 descartável na porta 55441. Incluiu a migration aditiva de email nullable e os testes DUAT. O cluster foi parado; nenhum teste abriu conexão no listener 5432. - Suite frontend: 117/117; `npm.cmd run types:check`; check dirigido de `simulation-contract.ts` e respectivo teste; `composer run types:check` (PHPStan, 0 erros); Pint dirigido; Vite build, 2.397 módulos; todos passaram. O build continua a emitir só aviso da dependência opcional `fontaine`. - Esta evidência actualiza a regressão técnica automatizada, não fecha a Etapa 11 global: continuam necessários leitor de ecrã/dispositivo real, revisão de produto e gates jurídicos/fiscais. A migration não foi aplicada à base local ou de produção. + ### Suite integral após testes do rollback DUAT — 2026-10-03 -Suite PHP `php artisan test`: 253/253 testes, 2.590 asserções, em PostgreSQL 17 isolado. Inclui os seis testes DUAT e os dois caminhos do rollback da migration. O cluster foi parado; o listener preexistente 5432 não recebeu conexão de teste. A etapa global mantém estado parcial por depender de validação manual, produto e gates legais/fiscais. \ No newline at end of file +Suite PHP `php artisan test`: 253/253 testes, 2.590 asserções, em PostgreSQL 17 isolado. Inclui os seis testes DUAT e os dois caminhos do rollback da migration. O cluster foi parado; o listener preexistente 5432 não recebeu conexão de teste. A etapa global mantém estado parcial por depender de validação manual, produto e gates legais/fiscais. diff --git a/docs/investmoz/simulations/stage-6-technical-traceability.md b/docs/investmoz/simulations/stage-6-technical-traceability.md index a952d028..0788669b 100644 --- a/docs/investmoz/simulations/stage-6-technical-traceability.md +++ b/docs/investmoz/simulations/stage-6-technical-traceability.md @@ -5,20 +5,20 @@ ## Rastreabilidade requisito–implementação–evidência -| Requisito do anexo | Implementação observada | Evidência automatizada | Estado | -| --- | --- | --- | --- | -| Investimento inicial, activos e fundo de maneio | `forecastBusinessPlan()` separa activos, inventário, recebíveis e valores a pagar; calcula usos iniciais e fundo de maneio líquido. | `financial forecast reconciles revenue, finance sources, working capital and monthly cash`; `losses and negative closing cash remain visible instead of being clamped to zero`. | Coberto | -| Produtos/serviços, quantidades, preços e custos variáveis | Linhas por produto; overrides mensais de unidades, preço e custo unitário. | `monthly unit schedules drive revenue, costs, inventory and scenario volume`; `monthly prices and unit costs affect revenue, consumption and scenarios`; horizontes/quantidades inválidas rejeitados. | Coberto | -| Compras, consumo e existências | Compras por período, prazo de entrega, stock inicial, recepção, consumo e falta estimada; agendas por produto sem transferência de stock entre linhas. | `inventory purchases, consumption, closing stock and stock shortfall reconcile independently of payments`; `inventory lead time separates monthly orders, receipts, shortages and goods still in transit`; `product inventory schedules reconcile independently and do not transfer stock between products`. | Coberto | -| Despesas operacionais e pessoal | Despesa reconhecida e pagamento mensal são entradas distintas; cronogramas têm de cobrir todo o horizonte. | `monthly operating and payroll expense schedules remain separate from payments`; `monthly operating schedules require full horizon, exact cents and current snapshot version`. | Coberto | -| Capital próprio, suprimentos, empréstimos e subvenções | Fontes iniciais são somadas separadamente dos usos; diferença positiva/negativa/zero tem estado explícito. | Teste principal de reconciliação financeira; interface informa falta ou excedente sem ajustar silenciosamente as entradas. | Coberto | -| Resultados previsionais e tesouraria mensal | Resultado operacional e antes de impostos são separados dos recebimentos/pagamentos e do saldo de caixa. | `financial forecast reconciles revenue, finance sources, working capital and monthly cash`; `principal repayments affect cash but are excluded from profit expenses`. | Coberto | -| Facturação versus cobrança; saldos de abertura | Cobrança com atraso/percentagem, recebíveis vencidos e parcelas de recebíveis de abertura entram em caixa apenas nos meses declarados; saldos por cobrar continuam visíveis. | `sales invoicing and receipts follow an optional collection lag`; `scheduled collection percentage keeps unpaid invoices in receivables`; testes de parcelas, limites e recuperação de recebíveis. | Coberto | -| Pagamentos, passivos e fornecedores | Pagamentos de custos/compras são separados do custo reconhecido; compras podem declarar lead time e parcelas, com montantes não pagos no horizonte expostos. | `monthly variable purchase payments affect cash independently from inventory orders and profit`; `supplier payment terms drive purchase cash flow and expose amounts due beyond the horizon`; `supplier order installments reconcile exact cents, cash dates and unallocated balances`. | Coberto no nível da encomenda | -| Juros versus capital reembolsado | Juros afectam resultado e pagamento; capital reembolsado reduz caixa, não despesa operacional. | `principal repayments affect cash but are excluded from profit expenses`. | Coberto | -| Depreciação contabilística versus fiscal | Depreciação linear configurada no plano é reconhecida apenas no resultado contabilístico; a ponte fiscal mantém matéria colectável indeterminada. | `depreciation allocation reconciles exactly over useful life`; exportação identifica matéria colectável como `indeterminate`. | Coberto sem inferência fiscal | -| Conversão cambial de apresentação | Moeda de apresentação exige taxa, data e fonte declaradas; conversão não altera moedas-base das entradas. | `presentation currency conversion requires and reports its rate, date, and declared source`. | Coberto | -| Pressupostos editáveis, snapshots e exportação reproduzível | Snapshots versionados restauram pressupostos; CSV inclui fórmulas, pressupostos, período, moeda, resultado, limitações e neutralização de fórmulas de spreadsheet. | `versioned plan snapshots restore assumptions and reject unsupported or oversized data`; `business plan export carries its assumptions, formulas, period, currency, and matching result`; testes de CSV mensal e de fórmulas. | Coberto | +| Requisito do anexo | Implementação observada | Evidência automatizada | Estado | +| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| Investimento inicial, activos e fundo de maneio | `forecastBusinessPlan()` separa activos, inventário, recebíveis e valores a pagar; calcula usos iniciais e fundo de maneio líquido. | `financial forecast reconciles revenue, finance sources, working capital and monthly cash`; `losses and negative closing cash remain visible instead of being clamped to zero`. | Coberto | +| Produtos/serviços, quantidades, preços e custos variáveis | Linhas por produto; overrides mensais de unidades, preço e custo unitário. | `monthly unit schedules drive revenue, costs, inventory and scenario volume`; `monthly prices and unit costs affect revenue, consumption and scenarios`; horizontes/quantidades inválidas rejeitados. | Coberto | +| Compras, consumo e existências | Compras por período, prazo de entrega, stock inicial, recepção, consumo e falta estimada; agendas por produto sem transferência de stock entre linhas. | `inventory purchases, consumption, closing stock and stock shortfall reconcile independently of payments`; `inventory lead time separates monthly orders, receipts, shortages and goods still in transit`; `product inventory schedules reconcile independently and do not transfer stock between products`. | Coberto | +| Despesas operacionais e pessoal | Despesa reconhecida e pagamento mensal são entradas distintas; cronogramas têm de cobrir todo o horizonte. | `monthly operating and payroll expense schedules remain separate from payments`; `monthly operating schedules require full horizon, exact cents and current snapshot version`. | Coberto | +| Capital próprio, suprimentos, empréstimos e subvenções | Fontes iniciais são somadas separadamente dos usos; diferença positiva/negativa/zero tem estado explícito. | Teste principal de reconciliação financeira; interface informa falta ou excedente sem ajustar silenciosamente as entradas. | Coberto | +| Resultados previsionais e tesouraria mensal | Resultado operacional e antes de impostos são separados dos recebimentos/pagamentos e do saldo de caixa. | `financial forecast reconciles revenue, finance sources, working capital and monthly cash`; `principal repayments affect cash but are excluded from profit expenses`. | Coberto | +| Facturação versus cobrança; saldos de abertura | Cobrança com atraso/percentagem, recebíveis vencidos e parcelas de recebíveis de abertura entram em caixa apenas nos meses declarados; saldos por cobrar continuam visíveis. | `sales invoicing and receipts follow an optional collection lag`; `scheduled collection percentage keeps unpaid invoices in receivables`; testes de parcelas, limites e recuperação de recebíveis. | Coberto | +| Pagamentos, passivos e fornecedores | Pagamentos de custos/compras são separados do custo reconhecido; compras podem declarar lead time e parcelas, com montantes não pagos no horizonte expostos. | `monthly variable purchase payments affect cash independently from inventory orders and profit`; `supplier payment terms drive purchase cash flow and expose amounts due beyond the horizon`; `supplier order installments reconcile exact cents, cash dates and unallocated balances`. | Coberto no nível da encomenda | +| Juros versus capital reembolsado | Juros afectam resultado e pagamento; capital reembolsado reduz caixa, não despesa operacional. | `principal repayments affect cash but are excluded from profit expenses`. | Coberto | +| Depreciação contabilística versus fiscal | Depreciação linear configurada no plano é reconhecida apenas no resultado contabilístico; a ponte fiscal mantém matéria colectável indeterminada. | `depreciation allocation reconciles exactly over useful life`; exportação identifica matéria colectável como `indeterminate`. | Coberto sem inferência fiscal | +| Conversão cambial de apresentação | Moeda de apresentação exige taxa, data e fonte declaradas; conversão não altera moedas-base das entradas. | `presentation currency conversion requires and reports its rate, date, and declared source`. | Coberto | +| Pressupostos editáveis, snapshots e exportação reproduzível | Snapshots versionados restauram pressupostos; CSV inclui fórmulas, pressupostos, período, moeda, resultado, limitações e neutralização de fórmulas de spreadsheet. | `versioned plan snapshots restore assumptions and reject unsupported or oversized data`; `business plan export carries its assumptions, formulas, period, currency, and matching result`; testes de CSV mensal e de fórmulas. | Coberto | ## Pendências delimitadas diff --git a/docs/investmoz/simulations/stage-7-technical-traceability.md b/docs/investmoz/simulations/stage-7-technical-traceability.md index 43ef762e..1bb37956 100644 --- a/docs/investmoz/simulations/stage-7-technical-traceability.md +++ b/docs/investmoz/simulations/stage-7-technical-traceability.md @@ -5,16 +5,16 @@ ## Rastreabilidade requisito–implementação–evidência -| Requisito do anexo | Implementação observada | Evidência automatizada | Estado | -| --- | --- | --- | --- | -| Mapear os resultados financeiros com origem e período | `buildFinancialFiscalBridge()` mapeia receita, custo de vendas, despesas operacionais, pessoal, depreciação e juros por mês, mantendo a designação contabilística e `taxTreatment: unclassified`. | `financial to fiscal bridge reconciles accounting result while leaving tax base indeterminate`. | Coberto | -| Distinguir resultado contabilístico de matéria colectável | Resultado antes de impostos reconcilia com as linhas; base tributável fica `indeterminate`, sem montante, e lista entradas em falta. | Mesmo teste verifica montante nulo e missing inputs; `financial bridge rejects a corrupted monthly cash schedule`. | Coberto sem inferência fiscal | -| Não presumir dedutibilidade de despesas | Nenhuma linha é marcada dedutível; bridge identifica classificação fiscal como pendente. | Testes da ponte verificam `unclassified` e reconciliação. | Coberto | -| Separar factura, cobrança, despesa e pagamento | Receitas/despesas contabilísticas são separadas dos recebimentos, pagamentos de fornecedor, custos operacionais, pessoal, juros e reembolso de principal no caixa. | `financial bridge separates opening receivables and scheduled sales collections`; `bridge maps monthly recognised expenses but keeps scheduled payments in cash only`; teste do plano confirma capital reembolsado fora das despesas. | Coberto | -| Proteger a projecção contra imposto desconhecido | Campo fiscal mensal vazio/omitido produz estado incompleto; saldo/fluxo líquidos totais ficam nulos/indeterminados e só são mostrados fluxos conhecidos antes do imposto. O utilizador pode declarar zero explicitamente. | `unknown assessed tax keeps the cash projection incomplete instead of assuming zero`; zero explícito passa como estimativa. | Corrigido e coberto | -| Separar crédito, pedido, reconhecimento e recebimento do IVA | Crédito, pedido e valor reconhecido não entram em caixa; só um recebimento explicitamente previsto, com mês e valor até ao reconhecimento, pode entrar na projecção. | `keeps VAT credit stages out of cash and includes only an explicitly forecast receipt`. | Coberto | -| Não duplicar cobranças/pagamentos nem aceitar snapshots inconsistentes | A ponte valida total facturado, cobrança por origem, saldo de recebíveis, movimentos líquidos e caixa de abertura/fecho antes de produzir o mapa. | `financial bridge rejects a corrupted monthly cash schedule`; testes de recebíveis e caixa mensal. | Coberto | -| Comparar cenários financeiros sem criar valores fiscais | Cenários favorável/desfavorável usam percentagens declaradas e mostram quais pressupostos mudaram; não criam taxas ou matéria tributável. | `financial scenarios expose user-declared changes without creating tax values`. | Coberto | +| Requisito do anexo | Implementação observada | Evidência automatizada | Estado | +| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| Mapear os resultados financeiros com origem e período | `buildFinancialFiscalBridge()` mapeia receita, custo de vendas, despesas operacionais, pessoal, depreciação e juros por mês, mantendo a designação contabilística e `taxTreatment: unclassified`. | `financial to fiscal bridge reconciles accounting result while leaving tax base indeterminate`. | Coberto | +| Distinguir resultado contabilístico de matéria colectável | Resultado antes de impostos reconcilia com as linhas; base tributável fica `indeterminate`, sem montante, e lista entradas em falta. | Mesmo teste verifica montante nulo e missing inputs; `financial bridge rejects a corrupted monthly cash schedule`. | Coberto sem inferência fiscal | +| Não presumir dedutibilidade de despesas | Nenhuma linha é marcada dedutível; bridge identifica classificação fiscal como pendente. | Testes da ponte verificam `unclassified` e reconciliação. | Coberto | +| Separar factura, cobrança, despesa e pagamento | Receitas/despesas contabilísticas são separadas dos recebimentos, pagamentos de fornecedor, custos operacionais, pessoal, juros e reembolso de principal no caixa. | `financial bridge separates opening receivables and scheduled sales collections`; `bridge maps monthly recognised expenses but keeps scheduled payments in cash only`; teste do plano confirma capital reembolsado fora das despesas. | Coberto | +| Proteger a projecção contra imposto desconhecido | Campo fiscal mensal vazio/omitido produz estado incompleto; saldo/fluxo líquidos totais ficam nulos/indeterminados e só são mostrados fluxos conhecidos antes do imposto. O utilizador pode declarar zero explicitamente. | `unknown assessed tax keeps the cash projection incomplete instead of assuming zero`; zero explícito passa como estimativa. | Corrigido e coberto | +| Separar crédito, pedido, reconhecimento e recebimento do IVA | Crédito, pedido e valor reconhecido não entram em caixa; só um recebimento explicitamente previsto, com mês e valor até ao reconhecimento, pode entrar na projecção. | `keeps VAT credit stages out of cash and includes only an explicitly forecast receipt`. | Coberto | +| Não duplicar cobranças/pagamentos nem aceitar snapshots inconsistentes | A ponte valida total facturado, cobrança por origem, saldo de recebíveis, movimentos líquidos e caixa de abertura/fecho antes de produzir o mapa. | `financial bridge rejects a corrupted monthly cash schedule`; testes de recebíveis e caixa mensal. | Coberto | +| Comparar cenários financeiros sem criar valores fiscais | Cenários favorável/desfavorável usam percentagens declaradas e mostram quais pressupostos mudaram; não criam taxas ou matéria tributável. | `financial scenarios expose user-declared changes without creating tax values`. | Coberto | ## Pendências e limites diff --git a/docs/investmoz/simulations/stage-8-technical-traceability.md b/docs/investmoz/simulations/stage-8-technical-traceability.md index ea781187..dd286385 100644 --- a/docs/investmoz/simulations/stage-8-technical-traceability.md +++ b/docs/investmoz/simulations/stage-8-technical-traceability.md @@ -5,16 +5,16 @@ ## Matriz requisito–implementação–evidência -| Requisito | Implementação | Evidência | Estado | -| --- | --- | --- | --- | -| Resumo antes do detalhe | O resultado aritmético apresenta o total indicativo; pressupostos, fórmula, contexto, versões e referência imutável ficam em secção expansível. Plano financeiro apresenta resumo, reconciliação, ponte fiscal e linhas mensais. | `completed arithmetic CSV preserves calculation details without exporting personal data`; browser responsivo final valida estado de resultado. | Coberto | -| Estados explícitos | Contratos distinguem calculado, estimado, indeterminado, incompleto e não aplicável; a ponte mantém matéria colectável indeterminada. Tesouraria agora mantém saldo total indeterminado quando imposto não foi informado. | `API v1 wire envelope preserves exact zero and validates indeterminate states`; `indeterminate results require missing inputs and cannot carry a numeric value`; teste Stage 7 do imposto em falta. | Coberto | -| Exportar sem autenticação e sem PII | CSV é construído localmente a partir dos dados da simulação actual; o exportador aritmético não inclui identidade/contactos. | `completed arithmetic CSV preserves calculation details without exporting personal data`; smoke público de cálculo no Edge QA e headers alvo verificados. | Coberto | -| Data, período, moeda, entradas, pressupostos e versões | CSV aritmético identifica exportação/resultado, período pontual sem período fiscal, execução, versão, relatório/hash e contexto; plano v17 e tesouraria v1 incluem data, horizonte, moeda, entradas, fórmulas e limitações. | `business plan export carries its assumptions, formulas, period, currency, and matching result`; teste de CSV da tesouraria e suites de simulador. | Coberto | -| Proveniência dos valores demonstrativos | Plano empresarial e tesouraria podem abrir com exemplos predefinidos; os CSV agora dizem que os valores vêm do formulário e que os valores iniciais podem ser exemplos. A interface de tesouraria também o anuncia antes da interpretação/exportação. | Testes de CSV do plano e tesouraria verificam a declaração de proveniência. | Corrigido nesta tranche | -| Reconciliar e preservar resultados guardados | Exportador de resultado só aceita execução concluída, snapshot decimal válido e envelope/total correspondentes; relatórios versionados exigem referência da revisão exacta e SHA-256. Snapshots dos planos preservam versões antigas. | `versioned arithmetic report reference requires the exact current snapshot and a valid hash`; `versioned arithmetic CSV refuses a result envelope that differs from the legacy total`; `versioned arithmetic CSV refuses a report from another scenario revision`; testes de snapshots empresariais v1–v17. | Coberto | -| Corrigir respostas e voltar a simular | A interface mantém respostas editáveis, reinicia/substitui o resultado no percurso existente e só exporta estados concluídos consistentes. | Testes dos contratos de criação/polling e navegador no formulário público. | Coberto tecnicamente | -| Segurança de CSV | Valores de texto fornecidos pelo utilizador são neutralizados contra fórmulas de planilha; números negativos legítimos permanecem numéricos. | `arithmetic result CSV neutralizes formula-like report metadata`; testes equivalentes de plano e tesouraria. | Coberto | +| Requisito | Implementação | Evidência | Estado | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| Resumo antes do detalhe | O resultado aritmético apresenta o total indicativo; pressupostos, fórmula, contexto, versões e referência imutável ficam em secção expansível. Plano financeiro apresenta resumo, reconciliação, ponte fiscal e linhas mensais. | `completed arithmetic CSV preserves calculation details without exporting personal data`; browser responsivo final valida estado de resultado. | Coberto | +| Estados explícitos | Contratos distinguem calculado, estimado, indeterminado, incompleto e não aplicável; a ponte mantém matéria colectável indeterminada. Tesouraria agora mantém saldo total indeterminado quando imposto não foi informado. | `API v1 wire envelope preserves exact zero and validates indeterminate states`; `indeterminate results require missing inputs and cannot carry a numeric value`; teste Stage 7 do imposto em falta. | Coberto | +| Exportar sem autenticação e sem PII | CSV é construído localmente a partir dos dados da simulação actual; o exportador aritmético não inclui identidade/contactos. | `completed arithmetic CSV preserves calculation details without exporting personal data`; smoke público de cálculo no Edge QA e headers alvo verificados. | Coberto | +| Data, período, moeda, entradas, pressupostos e versões | CSV aritmético identifica exportação/resultado, período pontual sem período fiscal, execução, versão, relatório/hash e contexto; plano v17 e tesouraria v1 incluem data, horizonte, moeda, entradas, fórmulas e limitações. | `business plan export carries its assumptions, formulas, period, currency, and matching result`; teste de CSV da tesouraria e suites de simulador. | Coberto | +| Proveniência dos valores demonstrativos | Plano empresarial e tesouraria podem abrir com exemplos predefinidos; os CSV agora dizem que os valores vêm do formulário e que os valores iniciais podem ser exemplos. A interface de tesouraria também o anuncia antes da interpretação/exportação. | Testes de CSV do plano e tesouraria verificam a declaração de proveniência. | Corrigido nesta tranche | +| Reconciliar e preservar resultados guardados | Exportador de resultado só aceita execução concluída, snapshot decimal válido e envelope/total correspondentes; relatórios versionados exigem referência da revisão exacta e SHA-256. Snapshots dos planos preservam versões antigas. | `versioned arithmetic report reference requires the exact current snapshot and a valid hash`; `versioned arithmetic CSV refuses a result envelope that differs from the legacy total`; `versioned arithmetic CSV refuses a report from another scenario revision`; testes de snapshots empresariais v1–v17. | Coberto | +| Corrigir respostas e voltar a simular | A interface mantém respostas editáveis, reinicia/substitui o resultado no percurso existente e só exporta estados concluídos consistentes. | Testes dos contratos de criação/polling e navegador no formulário público. | Coberto tecnicamente | +| Segurança de CSV | Valores de texto fornecidos pelo utilizador são neutralizados contra fórmulas de planilha; números negativos legítimos permanecem numéricos. | `arithmetic result CSV neutralizes formula-like report metadata`; testes equivalentes de plano e tesouraria. | Coberto | ## Alterações desta auditoria diff --git a/docs/investmoz/simulations/stage-9-technical-traceability.md b/docs/investmoz/simulations/stage-9-technical-traceability.md index 61c04297..19bc2139 100644 --- a/docs/investmoz/simulations/stage-9-technical-traceability.md +++ b/docs/investmoz/simulations/stage-9-technical-traceability.md @@ -3,18 +3,18 @@ **Data:** 2026-10-03 **Estado proposto:** conversão consentida para rascunho validada tecnicamente; gate global parcial. -| Requisito | Implementação e evidência | Estado / limite | -|---|---|---| -| Só reutilizar uma simulação com consentimento explícito | `AttachSimulationToDraftRequest` exige `consent_reuse=true`; `SimulationFormDraftTest::test_completed_simulation_is_attached_with_explicit_consent_as_an_immutable_versioned_snapshot` verifica recusa sem consentimento e conversão autorizada. | Coberto. | -| Autorizar acesso à simulação de origem | `SimulationFormDraftController::attachSimulation` verifica titularidade, token anónimo ou política de acesso à simulação e organização; teste de simulação alheia verifica resposta não autorizada. | Coberto por testes de feature. | -| Preservar a origem e a integridade do resultado | O controller exige execução concluída, snapshot e relatório prontos, hash válido e igualdade entre os snapshots canónicos. O serviço guarda referência e snapshot da origem imutáveis no histórico do rascunho. | Coberto. A conversão não altera a simulação original. | -| Evitar duplicação e concorrência perdida | `SimulationFormDrafts::attachSimulation` usa transação, lock da linha, deduplicação por `simulation_id` e `If-Match`/ETag; o replay não incrementa a versão. Testes cobrem repetição e versão desactualizada. | Coberto. | -| Permitir retirar o consentimento de reutilização | Atualização com `reuse_simulation=no` remove as fontes anexadas; o teste verifica que a leitura posterior não as apresenta. | Coberto. | -| Não aceitar fontes forjadas pelo cliente | `StoreSimulationFormDraftRequest` só aceita campos permitidos e não aceita `simulation_sources`; testes verificam a rejeição da origem forjada. | Coberto. | -| Documentar o contrato público | OpenAPI descreve `POST /simulation-drafts/{id}/conversions`, consentimento, ETag, deduplicação e respostas de erro; `tests/frontend/simulation-draft-conversion-openapi.test.mjs` valida a rota. | Coberto. | -| Não bloquear manifestação directa quando o cálculo fiscal está incompleto | O resultado concluído mantém a ligação pública `/manifestar-interesse`, que não recebe o snapshot nem depende de conversão/autenticação. O CTA informa que os dados da simulação não são partilhados. | Coberto pelo teste `simulator-entry.test.mjs`; a rota pública existente é coberta por `PublicPortalNavigationTest`. | -| Submeter formalmente o projecto / integrar com CRM | A API apenas anexa a simulação ao rascunho do formulário de investidor. Não cria Project, submissão formal nem entidade CRM. | Fora do comportamento implementado; requer fluxo e gate próprios. | -| Validar o fluxo no browser e aprovar o produto | Não há nesta tranche evidência de ensaio manual completo com leitor de ecrã/dispositivo, nem aprovação operacional de submissão. | Pendente para o gate global. | +| Requisito | Implementação e evidência | Estado / limite | +| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | +| Só reutilizar uma simulação com consentimento explícito | `AttachSimulationToDraftRequest` exige `consent_reuse=true`; `SimulationFormDraftTest::test_completed_simulation_is_attached_with_explicit_consent_as_an_immutable_versioned_snapshot` verifica recusa sem consentimento e conversão autorizada. | Coberto. | +| Autorizar acesso à simulação de origem | `SimulationFormDraftController::attachSimulation` verifica titularidade, token anónimo ou política de acesso à simulação e organização; teste de simulação alheia verifica resposta não autorizada. | Coberto por testes de feature. | +| Preservar a origem e a integridade do resultado | O controller exige execução concluída, snapshot e relatório prontos, hash válido e igualdade entre os snapshots canónicos. O serviço guarda referência e snapshot da origem imutáveis no histórico do rascunho. | Coberto. A conversão não altera a simulação original. | +| Evitar duplicação e concorrência perdida | `SimulationFormDrafts::attachSimulation` usa transação, lock da linha, deduplicação por `simulation_id` e `If-Match`/ETag; o replay não incrementa a versão. Testes cobrem repetição e versão desactualizada. | Coberto. | +| Permitir retirar o consentimento de reutilização | Atualização com `reuse_simulation=no` remove as fontes anexadas; o teste verifica que a leitura posterior não as apresenta. | Coberto. | +| Não aceitar fontes forjadas pelo cliente | `StoreSimulationFormDraftRequest` só aceita campos permitidos e não aceita `simulation_sources`; testes verificam a rejeição da origem forjada. | Coberto. | +| Documentar o contrato público | OpenAPI descreve `POST /simulation-drafts/{id}/conversions`, consentimento, ETag, deduplicação e respostas de erro; `tests/frontend/simulation-draft-conversion-openapi.test.mjs` valida a rota. | Coberto. | +| Não bloquear manifestação directa quando o cálculo fiscal está incompleto | O resultado concluído mantém a ligação pública `/manifestar-interesse`, que não recebe o snapshot nem depende de conversão/autenticação. O CTA informa que os dados da simulação não são partilhados. | Coberto pelo teste `simulator-entry.test.mjs`; a rota pública existente é coberta por `PublicPortalNavigationTest`. | +| Submeter formalmente o projecto / integrar com CRM | A API apenas anexa a simulação ao rascunho do formulário de investidor. Não cria Project, submissão formal nem entidade CRM. | Fora do comportamento implementado; requer fluxo e gate próprios. | +| Validar o fluxo no browser e aprovar o produto | Não há nesta tranche evidência de ensaio manual completo com leitor de ecrã/dispositivo, nem aprovação operacional de submissão. | Pendente para o gate global. | ## Verificação executada diff --git a/docs/investmoz/simulator-evolution-progress.md b/docs/investmoz/simulator-evolution-progress.md index ac582989..a5ea2395 100644 --- a/docs/investmoz/simulator-evolution-progress.md +++ b/docs/investmoz/simulator-evolution-progress.md @@ -27,38 +27,39 @@ Este relatório segue a sequência do anexo “Evolução sequencial do simulado Esta tabela substitui os estados/resumos anteriores quando divergirem; o detalhe histórico permanece abaixo. -| Etapa | Estado técnico actual | Próxima lacuna determinante | -| --- | --- | --- | -| 0 — diagnóstico | Concluída | Revisão manual de produto. | -| 1 — contrato/API | Concluída tecnicamente | Aceitação operacional geral. | -| 2 — jornadas/formulário | Parcial; validação automatizada extensa | Leitor de ecrã/dispositivo físico e revisão humana após o novo modo fiscal. | -| 3 — calculadoras | Parcial, educativas | Revisão financeira institucional e necessidades pessoais adicionais. | -| 4 — fontes/governação | Parcial; gate legal pendente | Textos integrais BR 186/187, parâmetros/fontes restantes e aprovação Fiscalista. | -| 5 — motor fiscal | DSL, DAG e resolvedor internos parciais; cálculo público bloqueado | Leitura segura/RLS, integração de snapshots/explicação e casos fiscais aprovados. | -| 6 — plano empresarial | Parcial, implementado em grande parte | Validação contabilística e cenários de incumprimento/fornecedores partilhados. | -| 7 — ponte fiscal | Parcial; matéria colectável indeterminada | Classificação fiscal aprovada; manter sem imposto conclusivo. | -| 8 — resultados/exportação | Parcial; CSV e snapshots implementados | Revisão assistiva; PDF não requerido pelo anexo. | -| 9 — conta/Project/CRM | Parcial | Submissão real a Project/CRM e revisão do fluxo institucional. | -| 10 — incentivos/aduaneiro/câmbio | Bloqueada para cálculo legal | Pauta/anexos íntegros, origem/câmbio e regras oficiais aprovadas. | -| 11 — regressão/entrega | Regressão automatizada aprovada; etapa parcial | Aceitação manual/assistiva, incidente QA histórico e gates institucionais. | +| Etapa | Estado técnico actual | Próxima lacuna determinante | +| -------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- | +| 0 — diagnóstico | Concluída | Revisão manual de produto. | +| 1 — contrato/API | Concluída tecnicamente | Aceitação operacional geral. | +| 2 — jornadas/formulário | Parcial; validação automatizada extensa | Leitor de ecrã/dispositivo físico e revisão humana após o novo modo fiscal. | +| 3 — calculadoras | Parcial, educativas | Revisão financeira institucional e necessidades pessoais adicionais. | +| 4 — fontes/governação | Parcial; gate legal pendente | Textos integrais BR 186/187, parâmetros/fontes restantes e aprovação Fiscalista. | +| 5 — motor fiscal | DSL, DAG e resolvedor internos parciais; cálculo público bloqueado | Leitura segura/RLS, integração de snapshots/explicação e casos fiscais aprovados. | +| 6 — plano empresarial | Parcial, implementado em grande parte | Validação contabilística e cenários de incumprimento/fornecedores partilhados. | +| 7 — ponte fiscal | Parcial; matéria colectável indeterminada | Classificação fiscal aprovada; manter sem imposto conclusivo. | +| 8 — resultados/exportação | Parcial; CSV e snapshots implementados | Revisão assistiva; PDF não requerido pelo anexo. | +| 9 — conta/Project/CRM | Parcial | Submissão real a Project/CRM e revisão do fluxo institucional. | +| 10 — incentivos/aduaneiro/câmbio | Bloqueada para cálculo legal | Pauta/anexos íntegros, origem/câmbio e regras oficiais aprovadas. | +| 11 — regressão/entrega | Regressão automatizada aprovada; etapa parcial | Aceitação manual/assistiva, incidente QA histórico e gates institucionais. | Regressao global mais recente: PHP 250/250 (2.582 assertions), frontend 115/115, TypeScript/PHPStan (0 erros), Pint e build (2.397 modulos) aprovados; smoke Edge em QA marcado passou. Leitor de ecrã/dispositivo e fontes fiscais continuam pendentes. + ## Matriz das etapas -| Etapa | Estado | Implementado/evidência | Lacunas e dependências | -| -------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 0 — diagnóstico | Concluída | Inspecção da estrutura, rotas, formulário, API, gates e testes; este registo actualizado. | Auditoria manual de UX não substitui validação com utilizadores. | -| 1 — arquitectura e contrato | Parcial concluída | `resources/js/lib/simulation-contract.ts` e `docs/investmoz/simulations/data-contracts.md` definem valores decimais como strings, contexto versionado, moeda e estados explícitos; testes cobrem invariantes. | Contrato ainda não aplicado a todas as respostas/persistência da API. | -| 2 — jornadas e formulário | Parcial | Percursos por perfil, menu/página pública, combobox por teclado e privacidade anónima; Edge desktop e mobile confirma layout sem overflow global e sem CTA sobre inputs. | Teste real com teclado/leitor de ecrã/dispositivo, revisão de produto e simplificação dos percursos partilhados continuam pendentes. | -| 3 — calculadoras | Parcial | Orçamento, prestações/amortização, taxas nominais/efectivas, margem, break-even, défice de financiamento e cronograma de crédito; precisão monetária em cêntimos. | Estimativas educativas; não integram ofertas de credores nem cobrem todo o orçamento pessoal. | -| 4 — fontes/governação fiscal | Parcial; gate bloqueado | Criado `docs/investmoz/simulations/official-source-register.md` com catálogos/páginas oficiais candidatos para AT, INSS, pauta aduaneira e câmbio e as lacunas de verificação. Nenhuma regra nova activada. | Textos integrais/consolidados, anexos, vigência, `scope_key`, classificação, parâmetros e aprovação do Fiscalista ainda pendentes. Gate humano necessário. | -| 5 — motor fiscal | Bloqueada para cálculo | `simulation-fiscal-status.tsx` mantém IRPS, INSS, IVA, IRPC, ISPC e retenções como indeterminados sem regra aprovada. | Sem fórmulas/valores fiscais públicos. O endpoint aritmético genérico não é motor fiscal. | -| 6 - plano empresarial | Parcial | Projecao mensal com agendas por produto para unidades, precos, custos, stock, encomendas, recepcao e parcelas de pagamento a fornecedores; recebiveis, despesas, pessoal, financiamento e tesouraria; snapshots v1-v16 e CSV testados. | Nao modela incumprimento nem fornecedores partilhados; validacao contabilistica institucional pendente. -| 7 — integração financeira/fiscal | Parcial | Ponte valida reconciliação contabilística, origens das cobranças, movimentos de recebíveis, pagamentos, fluxo líquido e saldos de caixa por mês. Matéria colectável continua indeterminada. | Classificação de dedutibilidade, ajustamentos e regras fiscais aprovadas ainda faltam; sem total fiscal ou aconselhamento conclusivo. | -| 8 — resultados e exportação | Parcial | CSV local para calculadoras, fluxo de caixa, plano e resultado aritmético concluído; inclui pressupostos, fórmulas, períodos, moeda ou ausência, fontes e limitações, sem dados pessoais; células neutralizam fórmulas de folha de cálculo. | Sem PDF/relatório assinado nem arquivo imutável; moeda fiscal e parâmetros legais continuam indisponíveis. | -| 9 — conta, projecto e CRM | Parcial | Conversão consentida, hash/versão íntegros, ETag e deduplicação; histórico paginado pessoal e de organizações com membership activa; regressões API/RLS executadas em PostgreSQL isolado. | Project/CRM não é submetido; revisão manual e validação do fluxo real de projecto pendentes. | -| 10 — incentivos/extensões | Bloqueada para regras | Nenhuma elegibilidade, benefício ou taxa sectorial presumida. A AT identifica uma Pauta de Serviço 2026 pesquisável com 5.822 códigos e anuncia Excel/PDF; nenhum ficheiro foi obtido ou parametrizado. | Faltam ficheiro/hashes e reconciliação integral da pauta, origem/preferências, fontes cambiais e sectoriais versionadas; extensões continuam inactivas. | -| 11 — verificação/entrega | Parcial | PHPUnit completo 230/230 (2.282 assertions), PHPStan global 0 erros, Pint dirigido, suite frontend, TypeScript, OpenAPI JSON e build Vite isolado passaram em 2026-10-03. | Sessão manual de acessibilidade/browser, integração real CRM/projecto e gates fiscais continuam pendentes; incidente `investmoz_test:5432` aguarda comparação com backup. | +| Etapa | Estado | Implementado/evidência | Lacunas e dependências | +| -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 — diagnóstico | Concluída | Inspecção da estrutura, rotas, formulário, API, gates e testes; este registo actualizado. | Auditoria manual de UX não substitui validação com utilizadores. | +| 1 — arquitectura e contrato | Parcial concluída | `resources/js/lib/simulation-contract.ts` e `docs/investmoz/simulations/data-contracts.md` definem valores decimais como strings, contexto versionado, moeda e estados explícitos; testes cobrem invariantes. | Contrato ainda não aplicado a todas as respostas/persistência da API. | +| 2 — jornadas e formulário | Parcial | Percursos por perfil, menu/página pública, combobox por teclado e privacidade anónima; Edge desktop e mobile confirma layout sem overflow global e sem CTA sobre inputs. | Teste real com teclado/leitor de ecrã/dispositivo, revisão de produto e simplificação dos percursos partilhados continuam pendentes. | +| 3 — calculadoras | Parcial | Orçamento, prestações/amortização, taxas nominais/efectivas, margem, break-even, défice de financiamento e cronograma de crédito; precisão monetária em cêntimos. | Estimativas educativas; não integram ofertas de credores nem cobrem todo o orçamento pessoal. | +| 4 — fontes/governação fiscal | Parcial; gate bloqueado | Criado `docs/investmoz/simulations/official-source-register.md` com catálogos/páginas oficiais candidatos para AT, INSS, pauta aduaneira e câmbio e as lacunas de verificação. Nenhuma regra nova activada. | Textos integrais/consolidados, anexos, vigência, `scope_key`, classificação, parâmetros e aprovação do Fiscalista ainda pendentes. Gate humano necessário. | +| 5 — motor fiscal | Bloqueada para cálculo | `simulation-fiscal-status.tsx` mantém IRPS, INSS, IVA, IRPC, ISPC e retenções como indeterminados sem regra aprovada. | Sem fórmulas/valores fiscais públicos. O endpoint aritmético genérico não é motor fiscal. | +| 6 - plano empresarial | Parcial | Projecao mensal com agendas por produto para unidades, precos, custos, stock, encomendas, recepcao e parcelas de pagamento a fornecedores; recebiveis, despesas, pessoal, financiamento e tesouraria; snapshots v1-v16 e CSV testados. | Nao modela incumprimento nem fornecedores partilhados; validacao contabilistica institucional pendente. | +| 7 — integração financeira/fiscal | Parcial | Ponte valida reconciliação contabilística, origens das cobranças, movimentos de recebíveis, pagamentos, fluxo líquido e saldos de caixa por mês. Matéria colectável continua indeterminada. | Classificação de dedutibilidade, ajustamentos e regras fiscais aprovadas ainda faltam; sem total fiscal ou aconselhamento conclusivo. | +| 8 — resultados e exportação | Parcial | CSV local para calculadoras, fluxo de caixa, plano e resultado aritmético concluído; inclui pressupostos, fórmulas, períodos, moeda ou ausência, fontes e limitações, sem dados pessoais; células neutralizam fórmulas de folha de cálculo. | Sem PDF/relatório assinado nem arquivo imutável; moeda fiscal e parâmetros legais continuam indisponíveis. | +| 9 — conta, projecto e CRM | Parcial | Conversão consentida, hash/versão íntegros, ETag e deduplicação; histórico paginado pessoal e de organizações com membership activa; regressões API/RLS executadas em PostgreSQL isolado. | Project/CRM não é submetido; revisão manual e validação do fluxo real de projecto pendentes. | +| 10 — incentivos/extensões | Bloqueada para regras | Nenhuma elegibilidade, benefício ou taxa sectorial presumida. A AT identifica uma Pauta de Serviço 2026 pesquisável com 5.822 códigos e anuncia Excel/PDF; nenhum ficheiro foi obtido ou parametrizado. | Faltam ficheiro/hashes e reconciliação integral da pauta, origem/preferências, fontes cambiais e sectoriais versionadas; extensões continuam inactivas. | +| 11 — verificação/entrega | Parcial | PHPUnit completo 230/230 (2.282 assertions), PHPStan global 0 erros, Pint dirigido, suite frontend, TypeScript, OpenAPI JSON e build Vite isolado passaram em 2026-10-03. | Sessão manual de acessibilidade/browser, integração real CRM/projecto e gates fiscais continuam pendentes; incidente `investmoz_test:5432` aguarda comparação com backup. | ### Etapa 11 — checklist de verificação global @@ -338,6 +339,7 @@ Rever este relatório e os diffs; encaminhar os bloqueios fiscais para validaç - Evidencia: `node --test tests/frontend/business-financial-plan.test.mjs` passou (23/23); `node --test tests/frontend/*.test.mjs` passou (80/80); `npm.cmd run types:check` passou; `vp fmt --check` passou nos quatro ficheiros de implementacao/teste/contrato; build Vite isolado passou (2.388 modulos; `%TEMP%\investmoz-stage6-monthly-units-20261003-003`); `git diff --check` passou com aviso preexistente de CRLF em `docs/investmoz/existing-business/form-schema.json`. - O formatter aplicado ao relatorio extenso de progresso indica inconsistencias nesse ficheiro; nao o reformatei integralmente para preservar secoes anteriores fora deste incremento. Nenhuma migration, base de dados, integracao ou ativacao externa foi tocada. - Complemento de interface: a agenda agora explica que meses fora do horizonte ficam guardados e que meses novos usam a quantidade-base. `vp check resources/js/components/simulation-business-plan.tsx` passou sem avisos nem erros; `node --test tests/frontend/*.test.mjs` passou (80/80); `npm.cmd run types:check` passou; build Vite isolado passou com 2.388 modulos em `%TEMP%\investmoz-stage6-monthly-units-20261003-005`; formatter dirigido e `git diff --check` passaram (com aviso de CRLF preexistente no schema do formulario empresarial). + ### Etapa 6 - precos e custos unitarios mensais (2026-10-03) - Snapshot corrente passa a `business-plan-v10`, preservando leitores v1-v9. Cada produto pode declarar agendas opcionais de preco e custo variavel por mes, cobrindo o horizonte e ate 60 meses; sem agenda, aplicam-se os valores base. Agendas antigas de unidades continuam compativeis. @@ -486,6 +488,7 @@ Rever este relatório e os diffs; encaminhar os bloqueios fiscais para validaç - Ficheiros: resources/js/lib/business-financial-plan.ts, resources/js/components/simulation-business-plan.tsx, tests/frontend/business-financial-plan.test.mjs, docs/investmoz/simulations/data-contracts.md e docs/investmoz/simulations/openapi.json. - Testes: plano empresarial 30/30; suite frontend 92/92; TypeScript passou; OpenAPI JSON valido; git diff --check passou com avisos preexistentes de CRLF/LF em documentos empresariais. Formatter aplicado aos tres ficheiros de codigo alterados. - Estado: incremento tecnico validado. Etapa 6 continua parcial ate stock por produto, prazos/condicoes por fornecedor e revisao institucional do modelo. + ### Etapa 6 - stock, compras e faltas por produto (2026-10-03) - A projeccao opcional por produto acrescenta saldo inicial, encomendas mensais e prazo de recepcao por produto. O consumo segue unidades e custo variavel do mesmo produto; recebimentos, stock em transito, saldo final e falta ficam discriminados e somados no total. @@ -495,6 +498,7 @@ Rever este relatório e os diffs; encaminhar os bloqueios fiscais para validaç - Evidencia: testes do plano 31/31; suite frontend 93/93; npm.cmd run types:check passou; vp fmt --check passou; OpenAPI JSON valido; git diff --check passou com avisos preexistentes CRLF/LF; build Vite isolado passou com 2.388 modulos (aviso opcional de fontaine ausente). - Reversao: retirar a opcao e o calculo v14; snapshots anteriores continuam validos. Nao foi criada migration nem alterada base de dados. - Estado: incremento tecnico validado. Etapa 6 continua parcial ate rever condicoes por fornecedor e modelo contabilistico com responsaveis institucionais. + ### Etapa 6 - prazos de pagamento por encomenda a fornecedor (2026-10-03) - Em modo de stock por produto, cada encomenda pode declarar prazo de pagamento de 0 a 12 meses desde a data da encomenda. Quando activo, estes prazos geram os pagamentos de compras no fluxo de caixa e substituem a agenda manual de pagamentos variaveis para evitar dupla contagem. @@ -589,6 +593,7 @@ Rever este relatório e os diffs; encaminhar os bloqueios fiscais para validaç ### Reconciliação do estado resumido (2026-10-03) Esta entrada substitui os totais antigos na matriz e checklist de Etapa 11 acima: a suíte PHP actualmente verificada é 230/230 (2.282 assertions), não 228/230, e o PHPStan global está limpo (0 erros). A validação de integração automatizada usa PostgreSQL/PostGIS isolado na porta QA 55441 e passou; apenas a validação manual com browser, leitor de ecrã e dispositivo móvel continua pendente. As afirmações anteriores de que nenhuma consulta/migração foi feita descrevem a fase inicial da auditoria, não o estado actual; houve testes nos clusters QA e o incidente separado `investmoz_test:5432` está documentado acima. Gates fiscais/legais e integração Project/CRM real continuam pendentes. + - Reforço do isolamento local em `phpunit.xml`: também fixados `DB_HOST=127.0.0.1` e `DB_PASSWORD` vazio, além de porta/base/papel QA. Verificação sem qualquer `DB_*` exportada no processo: PHPUnit carregou os padrões XML e o teste de histórico passou (1/1, 15 assertions) na porta 55441. As variáveis definidas pelo workflow CI prevalecem porque já existem no processo. ### Etapa 2/11 — verificação de browser e correcção de sobreposição móvel (2026-10-03) @@ -959,6 +964,7 @@ Rollback deste artefacto derivado: remover apenas `pii-26-30-review-only.csv` e ### Registo de autorização do utilizador (2026-10-03) O utilizador instruiu que validações e autorizações pendentes sejam consideradas autorizadas/válidas para prosseguir. Aplica-se como autorização do proprietário do projecto para continuar as tarefas técnicas e decisões internas reversíveis, sem nova confirmação rotineira. Não representa prova de conteúdo de diplomas, parecer de Fiscalista, validação de regulador/credor, execução de testes que não ocorreram ou aprovação por terceiros. Fontes e regras fiscais sem texto oficial verificável continuam sem cálculo/publicação; publicação de produção e activações externas continuam fora do âmbito autorizado pela especificação consolidada. + - Regressão frontend completa após a correcção: `node --test` sobre todos os `tests/frontend/*.test.mjs` passou 113/113. ### Revalidação das Etapas 6–7 — plano financeiro e ponte contabilístico-fiscal (2026-10-03) @@ -998,6 +1004,7 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - `SimulationTaxRuleGovernanceController` valida definições que declaram esta linguagem. Nenhum endpoint público de cálculo invoca o avaliador; não há taxas nem regras fiscais predefinidas. - `tests/Unit/SimulationTaxRuleExpressionTest.php`: 3 testes/9 asserções, incluindo cálculo sintético, ausência de valores, ciclo, operação desconhecida e campos extra. PHP 8.5.11, Pint e PHPStan verificados; `git diff --check` sem erros (avisos CRLF em documentos preexistentes). - Isto cobre a infraestrutura DSL, não explicabilidade com fonte legal, snapshots de regra, casos dourados fiscais, aprovação de Fiscalista nem activação pública. Etapa 5/antiga Etapa 10 permanece BLOQUEADA para cálculo fiscal de produção. + ### Parametrização e modos da simulação fiscal (2026-10-03) - Os registos `investmoz.tax.v1` validam parâmetros como `{ value, unit, conditions }`; nova versão de regra pode preservar a mesma AST e alterar apenas valores/unidade/condições. A aprovação e a publicação continuam no fluxo segregado já existente. @@ -1007,6 +1014,7 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - O resolvedor assinala mudança de vigência dentro do período e sobreposição como indeterminadas; nunca escolhe a taxa mais recente. Testes dirigidos 6/6 (18 asserções), TypeScript, Pint, rota, JSON OpenAPI e Vite build passaram nesta extensão. A leitura depende da migration de policy RLS estar aplicada. - A fonte SHA continua declarada e não é comparada automaticamente com exemplar arquivado. O endpoint não está ligado ao job de cálculo nem a snapshots fiscais guardados; não altera resultados históricos nem calcula imposto. Sem parâmetros aprovados correspondentes, a UI permanece indeterminada. Fontes oficiais e casos dourados ainda aguardam validação institucional. - Gate fiscal segue **BLOQUEADO para cálculo/publicação legal**. A camada de planeamento hipotético pode ser usada separadamente, sem conclusão de imposto devido. + ### Revalidação global pós-parametrização fiscal (2026-10-03) - A suite PHP completa foi repetida depois das alterações ao resolvedor e ao modo personalizado: `php vendor/bin/phpunit` passou **245/245 testes, 2.539 asserções** (87,5 s), na base isolada `investmoz_test`, PostgreSQL 17.11/PostGIS 3.6 UTF-8, porta 55441, executada como `postgres` exclusivamente no cluster QA. `php artisan migrate --force` respondeu `Nothing to migrate`; nenhuma migration foi executada. @@ -1027,6 +1035,7 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - O PHPStan global detectou inicialmente 10 erros de tipagem no modulo DUAT presente no worktree. Corrigidos tipos documentados do snapshot cifrado, obtencao tipada do payload validado, teste explicito do resultado de `filter_var`, e verificacao do retorno de `hash_file`; nao foram suprimidos diagnosticos. Reexecucao `composer run types:check`: 0 erros. Pint dirigido corrigiu estilo nos dois ficheiros PHP novos; `pint --test` e `php -l` passaram. - `php artisan test --filter=LandAssistanceTest` nao chegou aos casos: 3 falhas de setup por recusa de ligacao ao PostgreSQL QA isolado `127.0.0.1:55441` (`investmoz_test`), confirmado sem listener. Nenhuma migracao correu e a porta 5432 nao foi usada nesta verificacao. - Estado da Etapa 11: validacao estatica e frontend actualizada; testes de integracao PHP deste worktree e validacao assistiva/producto continuam pendentes. Etapas 6/7 mantem revisao contabilistica/fiscal institucional como gate; sem mudanca nas regras nem nos resultados fiscais. + ### Regressao PHP isolada e correcao do consentimento do rascunho — 2026-10-03 - A suite focada `LandAssistanceTest` identificou que os campos de declaracao/consentimento eram validados como `accepted` mesmo ao guardar um rascunho. Ajustada a validacao para aceitar os estados booleanos no rascunho; o FormRequest continua a exigir confirmacao verdadeira somente na transicao de pedido de apoio. Suite focada repetida: 3/3 testes, 38 assertions. @@ -1034,6 +1043,7 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - PostgreSQL QA foi encerrado com `pg_ctl stop -m fast`; `pg_ctl status` confirmou servidor parado e nao existe listener em 55441. Os ficheiros de QA ficam apenas em `%TEMP%` para auditoria, sem processo activo. - Revalidacao: frontend completo 115/115; TypeScript passou; Vite build passou (2.397 modulos; aviso nao impeditivo da dependencia opcional `fontaine`); PHPStan global 0 erros; Pint dirigido, `php -l` dos ficheiros DUAT e `git diff --check` nao foram repetidos apos este paragrafo; PHP lint/Pint ja passaram antes da suite, e Pint/PHPStan/PHP lint foram novamente executados depois dela. - A Etapa 11 fica com regressao automatizada actualizada e verde. Permanecem pendentes ensaio manual com leitor de ecrã/dispositivo, validacao de produto e gates institucionais; as Etapas 4/5/10 continuam sem activacao fiscal/legal. Nenhuma regra foi alterada ou publicada. + ### Etapa 2/11 — revalidacao responsiva e acessivel com Edge em QA marcado (2026-10-03) - Corrigido o smoke runner `tools/simulator-a11y-browser.mjs`: as condicoes de espera devolvem booleano, evitando serializar referencias DOM circulares pelo CDP; erros CDP agora identificam a chamada. O runner consegue exigir um marcador de alvo recebido no proprio response API. @@ -1042,6 +1052,7 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - PostgreSQL QA mostrou 0 simulacoes e 0 drafts apos o smoke. Edge, PHP/18089 e PostgreSQL/55441 foram parados; 55441, 18089 e 9224 ficaram sem listener. 5432 nao foi usada. O cluster e o perfil de browser permanecem inactivos em `%TEMP%`; nenhum dado real foi criado. - Estado Etapa 2: validacao automatizada de browser/foco/layout reforcada. Continua parcial: nao houve ensaio com leitor de ecrã real/dispositivo fisico nem revisao de produto. Etapa 11 continua parcial pelos mesmos gates humanos, apesar de regressao automatizada PHP/frontend e analise estatica verdes. - Adenda de isolamento do smoke: o router QA foi reforcado para fazer bootstrap da configuracao Laravel, comparar driver/host/porta/base antes de abrir qualquer ligacao e consultar `current_database()`, host e porta na ligacao efectiva; o header QA so e enviado depois dessas verificacoes. A repeticao final Edge passou e esta guardada em `docs/investmoz/simulations/simulator-browser-audit-2026-10-03-rerun-runtime-verified.json` (`qaTargetVerified=true`). A contagem final continua 0 simulacoes/0 drafts. Depois de terminar o servidor PHP, `pg_ctl` ja nao encontrou processo PostgreSQL e as verificacoes confirmaram sem listener em 55441/18089/9224; a pasta QA continua parada em `%TEMP%`. + ### Revisão do módulo Terra e DUAT face ao prompt consolidado (2026-10-03) - Inspeccionados os ficheiros do módulo, rotas, FormRequest, persistência cifrada, integração CRM, views, migration, testes e inventário. As sete digitalizações estão presentes fora do repositório: duas em `Formulario`, duas em `exploracao` e três em `Consulta`; a classificação deriva apenas das subpastas, sem OCR/revisão. O Guia Nacional e o texto oficial/Boletim da Lei de Terras não foram localizados em cópia legível. `DUAT.docx` foi encontrado, mas o registo anterior informa que estava bloqueado e não foi lido. @@ -1059,35 +1070,40 @@ O utilizador instruiu que validações e autorizações pendentes sejam consider - Adenda adicional ao registo cronológico: fontes oficiais INM confirmam que a Lei 3/2012 alterou artigos do CIVA 2007 e que a Lei 3/2025 alterou o artigo 9(13); uma apresentação oficial do MEF aponta a Lei 5/2020 como alteração intercalar desse artigo. Corrigida a sequência nos registos para incluir Lei 3/2012 como antecedente absorvido pela republicação 13/2016 e Leis 5/2020, 16/2020, 22/2022, 3/2025 e 10/2025 posteriores, com a vigência da 5/2020 ainda por verificar no BR. `git diff --check` passou nos documentos desta adenda; não foram necessários testes de código por se tratar de revisão documental. - ### Ajuste funcional do pedido de apoio Terra/DUAT — contacto telefónico (2026-10-03) - O prompt consolidado do módulo DUAT exige um contacto utilizável, sem exigir email e telefone em simultâneo. O formulário já permitia telefone, mas a submissão de apoio e o CRM bloqueavam email vazio. - A migration aditiva `2026_10_03_210000_make_interest_request_email_nullable` permite registar telefone isolado. Email vazio é normalizado para `NULL`; não é criado endereço sintético. O rollback fica bloqueado se já existirem pedidos sem email. - A conversão CRM de pedido telefónico para lead continua bloqueada até confirmação de email, pois a deduplicação/identidade CRM actual assenta nesse campo. O caso continua na fila interna para acompanhamento. - Foram adicionados testes para consentimento obrigatório e gravação de pedido telefónico. `php -l` passou nos seis ficheiros PHP alterados. `php artisan test --filter=LandAssistanceTest` não conseguiu executar: PostgreSQL de QA `investmoz_test` em `127.0.0.1:55441` recusou ligação; nenhum acesso à base operacional ocorreu. A validação de integração permanece pendente de uma instância isolada activa. + ### Plano financeiro empresarial — pagamento de valores a pagar de abertura (2026-10-03) - Corrigido o modelo para permitir parcelas mensais explicitamente declaradas dos valores a pagar existentes no início do período. Os pagamentos diminuem caixa e passivo; não diminuem lucro nem despesas do período. Sem calendário, nenhum pagamento é presumido e o saldo integral fica visível no fim do horizonte. - Percentagens são calculadas sobre o saldo original, não podem exceder 100% no conjunto, são arredondadas ao cêntimo e limitadas ao saldo ainda aberto. O snapshot passou a `business-plan-v17`; versões v1-v16 permanecem legíveis e restauram calendário vazio. CSV e interface mostram parcelas, pagamentos e saldo pendente. - Evidência: `node --test tests/frontend/business-financial-plan.test.mjs` 35/35; `node --test tests/frontend/*.test.mjs` 117/117; `npm.cmd run types:check`, `composer run types:check`/PHPStan (0 erros), Pint no escopo alterado e `npm.cmd run build` (2.397 módulos) passaram. O build emitiu apenas aviso sobre pacote opcional `fontaine`. + ### Etapa 1 — validação do snapshot na fronteira da execução (2026-10-03) - O guard da resposta de execução aceitava qualquer objecto como `result_snapshot` e deixava a validação para a apresentação. Agora, estados concluídos requerem snapshot v1 internamente coerente ou snapshot legado decimal legível; snapshots v1 inválidos e estados concluídos sem snapshot são rejeitados antes de entrarem no estado da interface. Estados pendentes, falhados e cancelados continuam a aceitar ausência/nulo; snapshots presentes precisam de ser reconhecíveis. - Testes específicos `node --test tests/frontend/simulation-contract.test.mjs`: 11/11; `npm.cmd run types:check` passou. Testes adicionais cobrem snapshot v1 válido, objecto inválido, ausente, versão inválida e legado válido. A regressão completa, formatação e build ficam registados abaixo após execução. + ### Actualização final das verificações desta retoma (2026-10-03) - A tentativa inicial DUAT na porta 55441 recusou ligação; foi criada uma nova instância PostgreSQL 17 isolada em `%TEMP%\investmoz-sequential-qa-20261003`, sem ligar à porta 5432. A primeira tentativa em 55442 descobriu que o `TestCase` só limpa o schema QA registado em 55441; o cluster foi movido para a porta autorizada do harness e o teste foi repetido sem alterar a lógica de produção para acomodar QA. - Após corrigir o fallback do email da conta quando o utilizador enviou explicitamente email vazio, `php artisan test --filter=LandAssistanceTest` passou 4/4 (42 asserções). Suite PHP integral: 251/251 (2.586 asserções), 69,2 s. A migration foi exercitada apenas neste cluster descartável. PostgreSQL QA foi parado; porta 55441 ficou livre; a porta 5432 permaneceu no listener preexistente e não foi consultada nem usada. - A verificação final do snapshot API adicionou validação na fronteira das execuções. Suite frontend antes da correcção automática de formatação: 117/117; `npm.cmd run check:fix` e check dirigido passaram depois. Build Vite passou com 2.397 módulos. A nova verificação não altera regras fiscais nem activa componentes bloqueadas. + ### Migration de contacto — verificação de reversibilidade e suite integral final (2026-10-03) - `LandAssistanceTest` passou 6/6 (46 asserções), incluindo dois testes directos da migration: reversão normal quando não há emails nulos e recusa segura quando já existem pedidos só telefónicos. A reversão não altera a coluna em caso de bloqueio. - Regressão PHP integral repetida após os novos testes: `php artisan test` passou 253/253, 2.590 asserções (63,2 s) em PostgreSQL 17 isolado. Cluster parado e `pg_ctl status` confirmou sem servidor; apenas listener preexistente em 5432 permaneceu, sem conexão de teste. - O rollback operacional é deliberadamente condicionado: primeiro tem de haver decisão sobre cada pedido telefónico para preencher email real ou preservar a migration. Não se elimina nem inventa contacto para tornar rollback possível. + ### Reconciliação da Etapa 1 — 2026-10-03 A linha de estado anterior que mantinha a Etapa 1 parcial foi revista contra as evidências existentes: `SimulationExecutionTest` valida questionário, report/jobs e round-trip do snapshot persistido; o contrato API do relatório compara payload/hash guardados e rejeita token alheio; os testes de drafts cobrem conversão, retoma e versão concorrente. A interface valida runtime os endpoints que actualmente consome. Questionnaire/jobs/report não são consumidos no React; os guards permanecem testados para futura integração e não se cria consumo artificial apenas para exercitá-los. Estado proposto corrigido para concluída tecnicamente, sujeito ao gate global da Etapa 11. + ### Etapa 2 — auditoria responsiva com cálculo isolado no browser (2026-10-03) - A matriz de Edge headless foi ampliada para 320, 360, 390, 768, 1024 e 1440 px nos estados de finalidade, operação fiscal e resultado. Foram verificados overflow horizontal, nomes acessíveis de controlos/botões/links e a árvore de acessibilidade. O formulário passou nas duas primeiras etapas; o resultado revelou overflow de um decimal longo em larguras 320–390 px. @@ -1142,23 +1158,27 @@ A linha de estado anterior que mantinha a Etapa 1 parcial foi revista contra as - `php artisan test --filter=SimulationFormDraftTest`: 10/10 testes, 79 asserções, PostgreSQL 17/PostGIS numa base descartável em 127.0.0.1:55441 com login não-superuser. Servidor parado após a execução; a base operacional na porta 5432 não foi usada. - A conversão anexa uma referência imutável ao rascunho do formulário. Não cria projecto, submissão formal nem registo CRM. Essa integração, a validação manual assistiva e a aprovação do fluxo pelo produto permanecem pendentes; Etapa 9/gate global continuam parciais. - Esta tranche foi read-only em relação ao código e aos dados da aplicação; só acrescentou evidência documental. Rollback: remover o novo mapa de rastreabilidade e esta adenda. + ### Etapa 5 do anexo actual / Etapa 10 histórica — reconciliação do motor fiscal (2026-10-03) - Auditoria detectou que `stage-10-gate.md` dizia que DSL/DAG e resolvedor estavam ausentes, apesar das implementações posteriores no worktree. Corrigi o estado documental sem substituir o histórico inicial: agora distingue infraestrutura técnica parcial de cálculo fiscal público bloqueado. - A matriz requisito→código→evidência está em `docs/investmoz/simulations/stage-10-technical-traceability.md`. A auditoria confirma evaluator allowlist/DAG, aritmética racional, arredondamento explícito, parâmetros por scope/vigência/contexto, governação versionada, RLS e trilho de eventos. - Evidência executada nesta retoma: DSL/resolvedor 9/9 testes, 27 asserções; governação 4/4 testes, 52 asserções em PostgreSQL 17/PostGIS isolado na porta 55441. Cluster parado; 5432 não foi usada. - Não há integração do avaliador com `RunSimulation`, explicabilidade fiscal completa no snapshot ou casos golden fiscais aprovados por Fiscalista. Nenhuma regra/taxa foi activada e nenhum código, migration ou dado foi alterado. + ### Etapa 10 do anexo — extensões informativas e territoriais (2026-10-03) - Inspeccionei a matriz do anexo e comparei as extensões com `InvestmentZones`, controladores de zona/relação/geometria, páginas públicas, fonte/vigência e origem da manifestação. O detalhe requisito→evidência→limite está em `docs/investmoz/simulations/stage-10-extension-traceability.md`. - O aviso público já deixa claro que localização/associação não atribui benefícios, licenças ou autorização aduaneira. A publicação depende de registo e fonte vigentes; relações e geometrias passam por revisão segregada. O mapa e as submissões preservam a proveniência/versão da zona. - `php artisan test --filter=InvestmentZoneTest`: 6/6 testes, 104 asserções, PHP 8.5.11, PostgreSQL 17/PostGIS QA isolado na porta 55441; servidor parado. O exercício de rollback da suite ocorreu exclusivamente no banco descartável. 5432 não foi usado. - Etapa 10 permanece parcial: não existem nesta evidência cálculo de incentivo, elegibilidade fiscal, cálculo pautal/cambial ou decisão de licenciamento/visto/trabalho. Não alterei código, fontes, parâmetros ou dados. + ### Etapa 11 — regressão global revalidada (2026-10-03) - `node --test tests/frontend/*.test.mjs`: 119/119; `npm.cmd run types:check`: aprovado; `composer run types:check`: PHPStan 0 erros; `npm.cmd run build`: aprovado, 2.397 módulos (aviso não impeditivo do pacote opcional `fontaine`). - Primeira suite PHP com login QA não-superuser terminou 250/253: três testes RLS falharam no setup ao criar `simulation_rls_test` por falta de `CREATEROLE`, não em asserções funcionais. Repetição completa em `investmoz_test` no cluster QA local usando o administrador do cluster: `php artisan test` passou 253/253, 2.590 asserções, 64,8 s. Tudo ocorreu em PostgreSQL 17/PostGIS isolado na porta 55441; o cluster foi parado após os testes e 5432 não foi usada. - Esta suite global demonstra regressão automatizada, não fecha leitor de ecrã/dispositivo, aprovação de produto nem os gates legais/fiscais. O uso do administrador ficou restrito à base de teste local e aos testes RLS que criam papel; nenhuma base compartilhada/produção foi usada. + ### Etapa 7 — calendário variável de imposto em tesouraria (2026-10-03) - Fechada a limitação documentada de um único montante fiscal mensal fixo. O planeador oferece modo fixo ou 12 valores por mês; montantes são pressupostos do utilizador, não cálculo fiscal. @@ -1283,6 +1303,7 @@ A linha de estado anterior que mantinha a Etapa 1 parcial foi revista contra as - Corrigidos tambem textos visiveis com codificacao corrompida encontrados no percurso. Evidencia compacta e limites: `docs/investmoz/simulations/simulator-journey-browser-2026-10-04.json`. - Verificacoes apos as correccoes: frontend 129/129; TypeScript, build Vite, PHPStan (0 erros), `SimulationFormDraftTest` 12/12 (91 assertions) e `git diff --check` passaram. O build compilou 2.399 modulos; aviso opcional de `fontaine` ausente. - A maturidade `Operacao` foi exercitada no simulador exploratorio; o CRUD autenticado do marketplace de negocios existentes nao fez parte deste ensaio. Dispositivo fisico e recuperacao depois de fechar o browser continuam pendentes; catalogos de financiamento, indicadores oficiais e prestadores de formacao permanecem lacunas claramente sinalizadas e nao bloquearam o percurso exploratorio. + ### Preparacao do piloto e auditoria de continuidade - 2026-10-04 - Preparei o roteiro de observacao de empreendedores e a folha de notas sem PII em `docs/investmoz/pilot/field-usability-pilot-2026-10.md`. O piloto real ainda depende de participantes voluntarios; nenhuma entrevista ou feedback foi inventado. @@ -1290,6 +1311,7 @@ A linha de estado anterior que mantinha a Etapa 1 parcial foi revista contra as - O rascunho anonimo e o autenticado usam ambitos distintos. Nao ha operacao de claim/transferencia. Tambem nao existe chave que ligue `simulation_form_drafts` a `existing_businesses`; este ultimo tem ownership, classificacao, revisoes e publicacao. Associar os dois automaticamente pode duplicar ou expor dados no fluxo errado. - A conta permite um unico rascunho activo. Claim/merge precisa resolver explicitamente conflito com dossier existente e manter historico; nao foi feita alteracao de codigo/dados por faltar semantica de produto para a relacao com o negocio registado. - Continuidade apos fechar browser e ligacao ao espaco autenticado ficam como primeiro trabalho tecnico depois da observacao inicial, com cenarios de recuperacao, ownership e duplicacao. A validacao Chromium anterior cobriu apenas sair da pagina e regressar na mesma sessao. + ### Continuidade e associação explícita de dossiers — 2026-10-04 - Implementada recuperação anónima durável com cookie aleatório `HttpOnly`, `SameSite=Lax`, âmbito `/api/v1`, `Secure` em HTTPS e validade deslizante de 30 dias. A base guarda apenas o hash do token; o formulário informa o prazo e esclarece que o acesso anónimo depende do mesmo navegador/dispositivo. A conta privada não expira por este mecanismo. @@ -1302,7 +1324,6 @@ A linha de estado anterior que mantinha a Etapa 1 parcial foi revista contra as - Estado do piloto: voluntários e aparelho físico continuam pendentes. A continuidade entre sessões já pode ser demonstrada tecnicamente, mas não deve ser chamada acompanhamento contínuo validado por utilizadores até observação do piloto. Catálogos documentados de financiamento, indicadores oficiais e prestadores de formação também permanecem pendentes e não bloqueiam o percurso exploratório. - Rollback: reverter apenas os ficheiros desta entrega; antes da migration `down`, exportar/rever associações e resolver dossiers múltiplos. A migration recusa rollback destrutivo quando há histórico de relação ou mais de um dossier por titular. Desactivar apenas a interface não remove nem invalida os tokens activos. - ### Preparação das sessões de utilização — 2026-10-04 - A apresentação provisória foi fixada como **“versão piloto com continuidade técnica testada”**; não existe validação com utilizadores. O roteiro passa a observar navegação autónoma, gravação/recuperação/associação, compreensão sobre dados declarados/estimados/verificados e utilidade do resultado para decidir o passo seguinte. diff --git a/docs/investmoz/suitecrm/mapping-catalog.md b/docs/investmoz/suitecrm/mapping-catalog.md index 0b4f5279..3a807613 100644 --- a/docs/investmoz/suitecrm/mapping-catalog.md +++ b/docs/investmoz/suitecrm/mapping-catalog.md @@ -19,15 +19,15 @@ O ambiente também não dispõe de Docker/Podman e `wsl --status` confirma que o ## Matriz de 15 campos de decisão -| Entidade de origem | Campos de origem já existentes | Módulo SuiteCRM candidato | Registo lógico de destino | Direcção prevista | Atributos lógicos candidatos | Relações candidatas | Sistema de registo | Finalidade permitida | Gate de consentimento/autoridade | Classificação | Minimização/transformação | Chave externa/idempotência | Controlo de acesso/auditoria | Estado/evidência exigida | -|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| -| `InterestRequest` / `interest_requests` | `integration_id`, `reference`, `full_name`, `email`, `phone`, `investor_type`, `country`, `sector_interest`, `investment_range`, `message`, `status`; consentimentos são registados separadamente | Leads | Manifestação comercial mínima, identificada no CRM como proveniente do portal | Portal → CRM, apenas após processamento aprovado da outbox | Nome de apresentação e meios de contacto estritamente necessários; referência do portal; categoria geral de interesse | Lead ligado a Contact/Account só após resolução humana/identificador externo | Portal mantém pedido e consentimento; CRM mantém tratamento comercial após aceitação | Responder à manifestação e acompanhar o pedido | Consentimento vigente para resposta; consentimento separado se houver partilha com proponente; finalidade, retenção e base legal aprovadas | Confidencial/PII | Excluir mensagem livre por defeito; não inferir residência, elegibilidade ou identidade; normalização sem alterar valor de origem | `integration_id` imutável e chave de integração única a definir no CRM; retry após escrita ambígua exige pesquisa antes de repetir | Papel técnico mínimo; logs apenas com operação/resultado/duração; acesso humano auditado | Parcial: intake e outbox transaccionais testados; não há worker, CRM nem esquema confirmado | -| `Investor` e relações `investor_contacts` | UUID do investidor; sectores, geografias, horizonte, ticket e relações a contactos | Contacts; Accounts para organização | Contact individual e Account organizacional, sem duplicar o perfil de investimento | Portal → CRM depois de identidade e vínculo revistos; eventual projecção CRM → portal ainda por decidir | Identificador externo; nome/contacto somente se fonte, finalidade e permissão autorizarem; organização e papel apenas após validação | Contact–Account e representação segundo relações comprovadas no CRM | Portal conserva perfil e preferências; CRM poderá ser principal para contacto comercial após corte | Gestão comercial de contacto e organização | Verificar consentimento, qualidade/origem, papel de representante e regras de acesso; sem equivalência automática de registos | Confidencial/PII | Não copiar preferências de investimento ou ticket sem necessidade documentada; NUIT e documentos ficam fora até aprovação específica | UUID externo do portal; restrição única e extensão de deduplicação a comprovar | Acesso restrito por equipa/território; trilho de alteração e origem | Pendente: resolução de identidade, política de campos e relação ainda não aprovadas | -| `Lead` e `CrmTask` locais | Estado, owner, investor, estimated amount, tarefas e respectivos estados | Leads; Tasks | Lead qualificado e actividade comercial | CRM → portal ou carga única durante transição; sem escrita dupla permanente | Estado, responsável, próxima acção e valores comerciais somente após definição do modelo de transição | Lead–Contact/Account; Task–Lead/Contact conforme API instalada | SuiteCRM após corte; actualmente o portal local | Qualificação e gestão comercial | Perfil autorizado, papéis institucionais e política de atribuição/SLA aprovados | Confidencial; PII onde aplicável | Não transportar comentários privados ou payload integral sem classificação e finalidade | UUID externo; estado e versão/correlação para reconciliação | Permissões CRM por papel, operação administrativa restrita, eventos auditados | Parcial: CRM local existe; ainda sem processo de migração, mappings de status ou ACL comprovada | -| `Opportunity` pública e `Project` | Slug/UUID, título, estado publicado, descrição pública, território, sector e referências | Módulo personalizado de referência ou leitura externa; não usar automaticamente `Opportunities` comercial | Espelho de catálogo publicado, se houver necessidade demonstrada | Portal → CRM, só dados já públicos e sincronização autorizada | Identificador e URL pública; título e estado público | Relação a Lead apenas por comando comercial revisto; não fundir com Opportunity do CRM | Portal | Contexto de referência para negociação | Aprovação do proprietário do catálogo e política de publicação | Público; referências internas podem ser confidenciais | Lista permitida de campos públicos; retirar geometria detalhada e metadados privados | UUID estável e versão do catálogo; upsert só depois de extensão única comprovada | Leitura por utilizadores comerciais autorizados; mudanças do catálogo auditadas no portal | Pendente: módulo, API, necessidade, contrato e política de actualização por validar | -| `EconomicCluster`, `DevelopmentCorridor`, `InvestmentZone` e relações | UUID/código, nome, estado, relações e geometrias/atributos GIS | Módulo de referência personalizado apenas se aprovado | Referência resumida a cluster/corredor/zona | Portal → CRM, somente identificadores e metadados aprovados | UUID, código, nome público e link do portal | Ligações a Project/Lead não podem alterar cadastro territorial | Portal | Contextualizar uma negociação | Aprovação do responsável do catálogo e classificação campo a campo | Público ou interno conforme o atributo | Nunca replicar geometrias completas, camadas GIS, coordenadas sensíveis ou dados de titularidade | UUID do portal e versão; sem chave única validada não sincronizar | Sem edição CRM; leitura restrita conforme classificação | Pendente: módulo/API e necessidade por provar | -| Reuniões, aftercare e seguimento institucional | Não há contrato de domínio SuiteCRM ligado ao portal; eventos são apenas propostos | Meetings, Cases, Tasks, Campaigns | Actividade comercial ou caso após investimento | CRM → portal por API/webhook assinado e projectado; comando validado via Laravel | Estado, data, responsável e resumo mínimo, conforme contrato a aprovar | Relações e ACL dependem dos módulos efectivamente instalados | CRM após corte; consentimentos e comprovativos continuam no portal | Seguimento comercial/aftercare autorizado | Base legal, participantes, finalidade e perfis de acesso aprovados | Confidencial/PII | Não sincronizar notas livres por defeito; resumos redigidos e auditáveis | ID CRM + UUID/correlation ID; inbox idempotente com assinatura e anti-replay | Autorização por pedido e papel; webhook validado; reconciliação de versão | Não implementado; módulos, webhook e projection inexistentes | -| `LandAssistanceCase`, anexos e assistência DUAT | Dados do requerente, finalidade, etapa, consentimento e referências de anexos | Sem módulo atribuído nesta fase | Nenhum registo no CRM até decisão específica | Nenhuma direcção aprovada | Nenhum | Nenhuma | Portal e arquivo privado autorizado | Apoio informativo/documental fundiário | Validação jurídica, consentimento específico e necessidade demonstrada | Sensível/PII e possivelmente documentos de alto risco | Não copiar anexos, dados de terra/titularidade ou informação jurídica para CRM por defeito | Não aplicável | Permissões e retenção específicas do portal | Bloqueado para integração; fluxo permanece separado e sem sincronização externa | +| Entidade de origem | Campos de origem já existentes | Módulo SuiteCRM candidato | Registo lógico de destino | Direcção prevista | Atributos lógicos candidatos | Relações candidatas | Sistema de registo | Finalidade permitida | Gate de consentimento/autoridade | Classificação | Minimização/transformação | Chave externa/idempotência | Controlo de acesso/auditoria | Estado/evidência exigida | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `InterestRequest` / `interest_requests` | `integration_id`, `reference`, `full_name`, `email`, `phone`, `investor_type`, `country`, `sector_interest`, `investment_range`, `message`, `status`; consentimentos são registados separadamente | Leads | Manifestação comercial mínima, identificada no CRM como proveniente do portal | Portal → CRM, apenas após processamento aprovado da outbox | Nome de apresentação e meios de contacto estritamente necessários; referência do portal; categoria geral de interesse | Lead ligado a Contact/Account só após resolução humana/identificador externo | Portal mantém pedido e consentimento; CRM mantém tratamento comercial após aceitação | Responder à manifestação e acompanhar o pedido | Consentimento vigente para resposta; consentimento separado se houver partilha com proponente; finalidade, retenção e base legal aprovadas | Confidencial/PII | Excluir mensagem livre por defeito; não inferir residência, elegibilidade ou identidade; normalização sem alterar valor de origem | `integration_id` imutável e chave de integração única a definir no CRM; retry após escrita ambígua exige pesquisa antes de repetir | Papel técnico mínimo; logs apenas com operação/resultado/duração; acesso humano auditado | Parcial: intake e outbox transaccionais testados; não há worker, CRM nem esquema confirmado | +| `Investor` e relações `investor_contacts` | UUID do investidor; sectores, geografias, horizonte, ticket e relações a contactos | Contacts; Accounts para organização | Contact individual e Account organizacional, sem duplicar o perfil de investimento | Portal → CRM depois de identidade e vínculo revistos; eventual projecção CRM → portal ainda por decidir | Identificador externo; nome/contacto somente se fonte, finalidade e permissão autorizarem; organização e papel apenas após validação | Contact–Account e representação segundo relações comprovadas no CRM | Portal conserva perfil e preferências; CRM poderá ser principal para contacto comercial após corte | Gestão comercial de contacto e organização | Verificar consentimento, qualidade/origem, papel de representante e regras de acesso; sem equivalência automática de registos | Confidencial/PII | Não copiar preferências de investimento ou ticket sem necessidade documentada; NUIT e documentos ficam fora até aprovação específica | UUID externo do portal; restrição única e extensão de deduplicação a comprovar | Acesso restrito por equipa/território; trilho de alteração e origem | Pendente: resolução de identidade, política de campos e relação ainda não aprovadas | +| `Lead` e `CrmTask` locais | Estado, owner, investor, estimated amount, tarefas e respectivos estados | Leads; Tasks | Lead qualificado e actividade comercial | CRM → portal ou carga única durante transição; sem escrita dupla permanente | Estado, responsável, próxima acção e valores comerciais somente após definição do modelo de transição | Lead–Contact/Account; Task–Lead/Contact conforme API instalada | SuiteCRM após corte; actualmente o portal local | Qualificação e gestão comercial | Perfil autorizado, papéis institucionais e política de atribuição/SLA aprovados | Confidencial; PII onde aplicável | Não transportar comentários privados ou payload integral sem classificação e finalidade | UUID externo; estado e versão/correlação para reconciliação | Permissões CRM por papel, operação administrativa restrita, eventos auditados | Parcial: CRM local existe; ainda sem processo de migração, mappings de status ou ACL comprovada | +| `Opportunity` pública e `Project` | Slug/UUID, título, estado publicado, descrição pública, território, sector e referências | Módulo personalizado de referência ou leitura externa; não usar automaticamente `Opportunities` comercial | Espelho de catálogo publicado, se houver necessidade demonstrada | Portal → CRM, só dados já públicos e sincronização autorizada | Identificador e URL pública; título e estado público | Relação a Lead apenas por comando comercial revisto; não fundir com Opportunity do CRM | Portal | Contexto de referência para negociação | Aprovação do proprietário do catálogo e política de publicação | Público; referências internas podem ser confidenciais | Lista permitida de campos públicos; retirar geometria detalhada e metadados privados | UUID estável e versão do catálogo; upsert só depois de extensão única comprovada | Leitura por utilizadores comerciais autorizados; mudanças do catálogo auditadas no portal | Pendente: módulo, API, necessidade, contrato e política de actualização por validar | +| `EconomicCluster`, `DevelopmentCorridor`, `InvestmentZone` e relações | UUID/código, nome, estado, relações e geometrias/atributos GIS | Módulo de referência personalizado apenas se aprovado | Referência resumida a cluster/corredor/zona | Portal → CRM, somente identificadores e metadados aprovados | UUID, código, nome público e link do portal | Ligações a Project/Lead não podem alterar cadastro territorial | Portal | Contextualizar uma negociação | Aprovação do responsável do catálogo e classificação campo a campo | Público ou interno conforme o atributo | Nunca replicar geometrias completas, camadas GIS, coordenadas sensíveis ou dados de titularidade | UUID do portal e versão; sem chave única validada não sincronizar | Sem edição CRM; leitura restrita conforme classificação | Pendente: módulo/API e necessidade por provar | +| Reuniões, aftercare e seguimento institucional | Não há contrato de domínio SuiteCRM ligado ao portal; eventos são apenas propostos | Meetings, Cases, Tasks, Campaigns | Actividade comercial ou caso após investimento | CRM → portal por API/webhook assinado e projectado; comando validado via Laravel | Estado, data, responsável e resumo mínimo, conforme contrato a aprovar | Relações e ACL dependem dos módulos efectivamente instalados | CRM após corte; consentimentos e comprovativos continuam no portal | Seguimento comercial/aftercare autorizado | Base legal, participantes, finalidade e perfis de acesso aprovados | Confidencial/PII | Não sincronizar notas livres por defeito; resumos redigidos e auditáveis | ID CRM + UUID/correlation ID; inbox idempotente com assinatura e anti-replay | Autorização por pedido e papel; webhook validado; reconciliação de versão | Não implementado; módulos, webhook e projection inexistentes | +| `LandAssistanceCase`, anexos e assistência DUAT | Dados do requerente, finalidade, etapa, consentimento e referências de anexos | Sem módulo atribuído nesta fase | Nenhum registo no CRM até decisão específica | Nenhuma direcção aprovada | Nenhum | Nenhuma | Portal e arquivo privado autorizado | Apoio informativo/documental fundiário | Validação jurídica, consentimento específico e necessidade demonstrada | Sensível/PII e possivelmente documentos de alto risco | Não copiar anexos, dados de terra/titularidade ou informação jurídica para CRM por defeito | Não aplicável | Permissões e retenção específicas do portal | Bloqueado para integração; fluxo permanece separado e sem sincronização externa | ## Contrato de identidade e de reconciliação diff --git a/docs/investmoz/territorial-search-audit-2026-10-04.md b/docs/investmoz/territorial-search-audit-2026-10-04.md index 73234cf3..ad922a24 100644 --- a/docs/investmoz/territorial-search-audit-2026-10-04.md +++ b/docs/investmoz/territorial-search-audit-2026-10-04.md @@ -10,14 +10,14 @@ A pesquisa territorial de oportunidades passou a filtrar no servidor por IDs da ## Módulos -| Módulo/percurso | Estado | Evidência ou limite | -|---|---|---| -| `/oportunidades` — página, `/api/v1/investment/search`, mapa e CSV | Corrigido | Usam `InvestmentCatalogue::query()`; hierarquia por UUID, locais primários e múltiplos, filtros combinados, sem duplicação. Testes sintéticos em duas províncias. | -| `/educacao` — procura de necessidades e capacidades | Corrigido | `geo_location_id` e cascata administrativa incluem descendentes por ID antes da paginação; 36 testes dirigidos passaram. | -| `/negocios` — catálogo público de negócios existentes | Corrigido | Filtro territorial usa `EXISTS` sobre localizações públicas; mais de uma localização não duplica anúncios. Suite do assistente/catálogo passou, mas falta um caso dedicado multi-localização neste módulo. | -| `/carteira` — projectos âncora, clusters e corredores | Corrigido parcialmente | Mantém filtros de publicação e considera territórios primários, locais públicos de projectos e locais públicos de corredores. Clusters não têm relação de localizações múltiplas identificada; ficam limitados ao território primário. | -| `/zonas` — ZEE, ZFI e parques | Corrigido parcialmente | Território primário ou geometria associada, validada e autorizada para publicação. Sem âmbito nacional explícito neste modelo; registos sem território não correspondem à seleção. Testes existentes passaram, sem fixture multi-geometria por províncias. | -| Rotas territoriais fora destes catálogos | Pendente de auditoria | Percursos de assistência fundiária, gestão e edição, relações de clusters/corredores, prestadores/cursos e demais pesquisas GIS ainda não foram uniformizados nem validados contra os critérios completos. Não declarar cobertura transversal concluída. | +| Módulo/percurso | Estado | Evidência ou limite | +| ------------------------------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `/oportunidades` — página, `/api/v1/investment/search`, mapa e CSV | Corrigido | Usam `InvestmentCatalogue::query()`; hierarquia por UUID, locais primários e múltiplos, filtros combinados, sem duplicação. Testes sintéticos em duas províncias. | +| `/educacao` — procura de necessidades e capacidades | Corrigido | `geo_location_id` e cascata administrativa incluem descendentes por ID antes da paginação; 36 testes dirigidos passaram. | +| `/negocios` — catálogo público de negócios existentes | Corrigido | Filtro territorial usa `EXISTS` sobre localizações públicas; mais de uma localização não duplica anúncios. Suite do assistente/catálogo passou, mas falta um caso dedicado multi-localização neste módulo. | +| `/carteira` — projectos âncora, clusters e corredores | Corrigido parcialmente | Mantém filtros de publicação e considera territórios primários, locais públicos de projectos e locais públicos de corredores. Clusters não têm relação de localizações múltiplas identificada; ficam limitados ao território primário. | +| `/zonas` — ZEE, ZFI e parques | Corrigido parcialmente | Território primário ou geometria associada, validada e autorizada para publicação. Sem âmbito nacional explícito neste modelo; registos sem território não correspondem à seleção. Testes existentes passaram, sem fixture multi-geometria por províncias. | +| Rotas territoriais fora destes catálogos | Pendente de auditoria | Percursos de assistência fundiária, gestão e edição, relações de clusters/corredores, prestadores/cursos e demais pesquisas GIS ainda não foram uniformizados nem validados contra os critérios completos. Não declarar cobertura transversal concluída. | ## Testes e browser diff --git a/docs/investmoz/unification/README.md b/docs/investmoz/unification/README.md index a5f50e5a..9b0e7353 100644 --- a/docs/investmoz/unification/README.md +++ b/docs/investmoz/unification/README.md @@ -21,21 +21,21 @@ Esta entrega não representa a conclusão integral do prompt: a matriz identific Fonte remota observada: https://teste-info.tempo.co.mz/portal-do-investidor, HTTP 200, HTML público com Inertia `investor-portal/welcome`. Código e base remotos estão noutra máquina, conforme confirmação do proprietário. Não foram inspeccionados nem alterados. A aplicação Laravel/React local é a base de consolidação autorizada. -| Domínio | Página remota observada | Aplicação local antes do upgrade | Decisão e regra | -| --- | --- | --- | --- | -| Entrada | `/portal-do-investidor`, menus e pesquisa | `/`, sete áreas e serviços | Consolidar: uma navegação; conservar URLs antigas com redireccionamento local. | -| Sectores | Mineração, Energia, Agronegócio, Turismo, Infraestruturas | Cadastro `sectors`, detalhe e pesquisa | Manter cadastro local; não substituir descrições por scraping. | -| Oportunidades | Dois projectos identificados como exemplos fictícios em USD | `opportunities`, publicação, detalhe, interesse | Manter; exemplos remotos não migram como factos. Preservar inventário e natureza demonstrativa. | -| Território | Atalho de províncias, sem mapa comprovado no HTML | 6 corredores, 8 clusters, 15 âncoras publicados em dossiers | Adaptar: ligações tipadas a oportunidades, pesquisa e navegação conjuntas; não duplicar dossiers como oportunidades. | -| Educação | Não observada na página inicial | Procura, capacidade, lacunas e propostas internas | Criar descoberta pública das oportunidades educativas publicadas; preservar dados internos. | -| Procedimentos | Cinco passos de orientação | Seis etapas e links de preparação | Consolidar no roteiro local, sem transformar orientação em exigência legal. | -| Legislação e documentos | Menus e âncoras | Catálogo, ficheiros privados e download autorizado | Manter; não inferir diplomas ou taxas a partir de links. | -| Parcerias | Âncora `#parcerias` | Propostas, moderação e contacto intermediado | Manter fluxo local. | -| Contactos | Morada e telefones públicos da Tempo; email protegido no HTML | Contactos configuráveis e formulário | Preservar referência no inventário; não substituir contactos de atendimento nem atribuir mandato institucional automaticamente. | -| Interesse | CTA nas fichas | API idempotente, formulário simples, CRM e jornadas | Adaptar com rascunho, etapas, necessidades e anexos privados; conservar API e pedidos existentes. | -| Área pessoal | Não comprovada na página consultada | Painel, balcão e autenticação | Consolidar entradas e relacionar pedidos, favoritos e manifestações. | -| Administração | Não acessível publicamente | Gestão, auditoria, educação, IA | Adaptar com relações, referências e versões; não conceder privilégios ao registo público. | -| URLs/duplicação | Prefixo `/portal-do-investidor` | Rotas sem prefixo | Descontinuar entradas duplicadas através de aliases; conservar registos e origem. | +| Domínio | Página remota observada | Aplicação local antes do upgrade | Decisão e regra | +| ----------------------- | ------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Entrada | `/portal-do-investidor`, menus e pesquisa | `/`, sete áreas e serviços | Consolidar: uma navegação; conservar URLs antigas com redireccionamento local. | +| Sectores | Mineração, Energia, Agronegócio, Turismo, Infraestruturas | Cadastro `sectors`, detalhe e pesquisa | Manter cadastro local; não substituir descrições por scraping. | +| Oportunidades | Dois projectos identificados como exemplos fictícios em USD | `opportunities`, publicação, detalhe, interesse | Manter; exemplos remotos não migram como factos. Preservar inventário e natureza demonstrativa. | +| Território | Atalho de províncias, sem mapa comprovado no HTML | 6 corredores, 8 clusters, 15 âncoras publicados em dossiers | Adaptar: ligações tipadas a oportunidades, pesquisa e navegação conjuntas; não duplicar dossiers como oportunidades. | +| Educação | Não observada na página inicial | Procura, capacidade, lacunas e propostas internas | Criar descoberta pública das oportunidades educativas publicadas; preservar dados internos. | +| Procedimentos | Cinco passos de orientação | Seis etapas e links de preparação | Consolidar no roteiro local, sem transformar orientação em exigência legal. | +| Legislação e documentos | Menus e âncoras | Catálogo, ficheiros privados e download autorizado | Manter; não inferir diplomas ou taxas a partir de links. | +| Parcerias | Âncora `#parcerias` | Propostas, moderação e contacto intermediado | Manter fluxo local. | +| Contactos | Morada e telefones públicos da Tempo; email protegido no HTML | Contactos configuráveis e formulário | Preservar referência no inventário; não substituir contactos de atendimento nem atribuir mandato institucional automaticamente. | +| Interesse | CTA nas fichas | API idempotente, formulário simples, CRM e jornadas | Adaptar com rascunho, etapas, necessidades e anexos privados; conservar API e pedidos existentes. | +| Área pessoal | Não comprovada na página consultada | Painel, balcão e autenticação | Consolidar entradas e relacionar pedidos, favoritos e manifestações. | +| Administração | Não acessível publicamente | Gestão, auditoria, educação, IA | Adaptar com relações, referências e versões; não conceder privilégios ao registo público. | +| URLs/duplicação | Prefixo `/portal-do-investidor` | Rotas sem prefixo | Descontinuar entradas duplicadas através de aliases; conservar registos e origem. | ## Arquitectura da informação @@ -49,17 +49,17 @@ As tabelas públicas de oportunidades, dossiers, projectos, educação e CRM man ## Backlog e critérios de aceitação -| Prioridade | Épico | Dependência | Aceitação | -| --- | --- | --- | --- | -| P0 | Inventário e consolidação | HTML público + código local | Divergências e origem documentadas; sem importação de exemplos como reais. | -| P0 | Modelo de contexto e referências | Esquema existente | FKs, pivots, filtros combináveis, gestão e rollback que preserva dados. | -| P0 | Manifestação progressiva | Autenticação + serviço de intake | Rascunho privado, versão, opções de BD, necessidades, consentimento, anexos e protocolo idempotente. | -| P1 | Descoberta unificada | Relações e publicação | Cluster/corredor/âncora/educação pesquisáveis e relacionados nas fichas. | -| P1 | Exploração | Catálogo | Lista/grelha, comparação, favoritos, exportação e mapa de dados geográficos autorizados. | -| P1 | Backoffice e rastreabilidade | Modelo + gates | Relações editáveis, opções desactiváveis sem perda histórica, decisões auditadas. | -| P1 | QA e migração | Implementação | Testes isolados, backup/restauro, migração aditiva, verificação móvel/desktop. | -| P2 | Homologação e operação remota | Acesso à outra máquina | Código/base reconciliados, ensaio de deploy/reversão e monitorização no destino. | -| P2 | Formação | Fluxos validados | Manual, exercícios e sessão com participantes reais; não declarar formação executada sem evidência. | +| Prioridade | Épico | Dependência | Aceitação | +| ---------- | -------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------- | +| P0 | Inventário e consolidação | HTML público + código local | Divergências e origem documentadas; sem importação de exemplos como reais. | +| P0 | Modelo de contexto e referências | Esquema existente | FKs, pivots, filtros combináveis, gestão e rollback que preserva dados. | +| P0 | Manifestação progressiva | Autenticação + serviço de intake | Rascunho privado, versão, opções de BD, necessidades, consentimento, anexos e protocolo idempotente. | +| P1 | Descoberta unificada | Relações e publicação | Cluster/corredor/âncora/educação pesquisáveis e relacionados nas fichas. | +| P1 | Exploração | Catálogo | Lista/grelha, comparação, favoritos, exportação e mapa de dados geográficos autorizados. | +| P1 | Backoffice e rastreabilidade | Modelo + gates | Relações editáveis, opções desactiváveis sem perda histórica, decisões auditadas. | +| P1 | QA e migração | Implementação | Testes isolados, backup/restauro, migração aditiva, verificação móvel/desktop. | +| P2 | Homologação e operação remota | Acesso à outra máquina | Código/base reconciliados, ensaio de deploy/reversão e monitorização no destino. | +| P2 | Formação | Fluxos validados | Manual, exercícios e sessão com participantes reais; não declarar formação executada sem evidência. | ## Limites de verificação diff --git a/docs/investmoz/unification/browser-validation.json b/docs/investmoz/unification/browser-validation.json index b322bd91..c76bbd7e 100644 --- a/docs/investmoz/unification/browser-validation.json +++ b/docs/investmoz/unification/browser-validation.json @@ -1,165 +1,165 @@ { - "date": "2026-09-27", - "environment": "APP_ENV=qa; isolated QA database; synthetic fixtures", - "reports": [ - { - "name": "education-mobile", - "title": "Oportunidades na Educação", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "opportunity-mobile", - "title": "Laboratório sintético de formação", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "workspace-mobile", - "title": "Área do investidor", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "profile-companies-mobile", - "title": "Perfil de investimento e empresas", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "identity-mobile", - "title": "Preparar manifestação de interesse", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "investment-mobile", - "title": "Preparar manifestação de interesse", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "needs-mobile", - "title": "Preparar manifestação de interesse", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "attachments-mobile", - "title": "Preparar manifestação de interesse", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "review-mobile", - "title": "Preparar manifestação de interesse", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "receipt-mobile", - "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "conversation-revision-mobile", - "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "notifications-mobile", - "title": "Notificações e acompanhamento", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "workspace-desktop", - "title": "Área do investidor", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "catalogue-desktop", - "title": "Catálogo de investimento", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "references-desktop", - "title": "Opções dos formulários e filtros", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "context-desktop", - "title": "Contexto: Laboratório sintético de formação", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "management-receipt-desktop", - "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 - }, - { - "name": "references-mobile", - "title": "Opções dos formulários e filtros", - "overflow": false, - "breadcrumb": true, - "supportVisible": true, - "unlabelled": 0 + "date": "2026-09-27", + "environment": "APP_ENV=qa; isolated QA database; synthetic fixtures", + "reports": [ + { + "name": "education-mobile", + "title": "Oportunidades na Educação", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "opportunity-mobile", + "title": "Laboratório sintético de formação", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "workspace-mobile", + "title": "Área do investidor", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "profile-companies-mobile", + "title": "Perfil de investimento e empresas", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "identity-mobile", + "title": "Preparar manifestação de interesse", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "investment-mobile", + "title": "Preparar manifestação de interesse", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "needs-mobile", + "title": "Preparar manifestação de interesse", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "attachments-mobile", + "title": "Preparar manifestação de interesse", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "review-mobile", + "title": "Preparar manifestação de interesse", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "receipt-mobile", + "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "conversation-revision-mobile", + "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "notifications-mobile", + "title": "Notificações e acompanhamento", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "workspace-desktop", + "title": "Área do investidor", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "catalogue-desktop", + "title": "Catálogo de investimento", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "references-desktop", + "title": "Opções dos formulários e filtros", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "context-desktop", + "title": "Contexto: Laboratório sintético de formação", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "management-receipt-desktop", + "title": "Manifestação IR-2026-01M3J34C62CKWV6WPG0H3B9SCE", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + }, + { + "name": "references-mobile", + "title": "Opções dos formulários e filtros", + "overflow": false, + "breadcrumb": true, + "supportVisible": true, + "unlabelled": 0 + } + ], + "errors": [], + "workflow": { + "csrfEnforced": true, + "draftSaved": true, + "stepPreserved": true, + "privateAttachment": true, + "receipt": true, + "idempotentReplay": true, + "managerContext": true, + "ownerIsolation": true, + "profileCompanyPrefill": true, + "conversation": true, + "revisions": true, + "notifications": true } - ], - "errors": [], - "workflow": { - "csrfEnforced": true, - "draftSaved": true, - "stepPreserved": true, - "privateAttachment": true, - "receipt": true, - "idempotentReplay": true, - "managerContext": true, - "ownerIsolation": true, - "profileCompanyPrefill": true, - "conversation": true, - "revisions": true, - "notifications": true - } -} \ No newline at end of file +} diff --git a/docs/investmoz/unification/data-and-operations.md b/docs/investmoz/unification/data-and-operations.md index 366c7810..5d6a1653 100644 --- a/docs/investmoz/unification/data-and-operations.md +++ b/docs/investmoz/unification/data-and-operations.md @@ -4,25 +4,25 @@ O esquema executável está em `database/migrations/2026_09_27_140000_investment_unification.php` e `2026_09_27_150000_investor_workspace.php`. Todas as relações têm chaves externas com eliminação restringida. Identificadores territoriais são UUID; oportunidades e utilizadores conservam os seus identificadores numéricos. -| Tabela | Chave / relação | Conteúdo e regra | -| --- | --- | --- | -| `investment_reference_options` | `id`; único `(group,code)` | Rótulo, actividade, ordem, obrigatoriedade de especificação. Códigos estáveis; desactivar não elimina referências históricas. | -| `opportunity_profiles` | PK/FK `opportunity_id` | Educação, modelo de negócio, requisitos, riscos, data-alvo, fonte, versão. Texto destinado à ficha pública após publicação. Não colocar aqui notas privadas. | -| `opportunity_clusters` | PK `(opportunity_id,cluster_id)` | Uma oportunidade pode pertencer a vários clusters. | -| `opportunity_corridors` | PK `(opportunity_id,corridor_id)` | Vários corredores por oportunidade. | -| `opportunity_projects` | PK `(opportunity_id,project_id)` | Vários projectos âncora por oportunidade. | -| `opportunity_locations` | PK `(opportunity_id,geo_location_id)` | Ligações às localizações existentes. Geometrias continuam em `geo_geometries`, PostGIS SRID 4326. | -| `opportunity_facets` | PK `(opportunity_id,option_id)` | Subsector, parceria, impacto, educação, infraestrutura, competências e necessidades configuráveis. | -| `investment_submissions` | UUID; dono `user_id`; recibo único `interest_request_id` | `payload` encriptado, versão, estado draft/submitted. No máximo um destino: oportunidade ou dossier; interesse geral admite nenhum. Submissão exige recibo e rascunho não o admite. | -| `investment_specific_needs` | `submission_id`; opções FK | Categoria, tipo, quantidade decimal não negativa, unidade, prioridade, prazo. Descrição, localização e demais detalhes são encriptados. | -| `investment_submission_attachments` | UUID; submissão/documento FK | Ficheiro privado em `documents`; nome, tipo, tamanho e checksum canónicos. | -| `investment_submission_events` | `submission_id`, actor, versão | Criação, gravação, anexo e submissão. Guarda actos e versões; não é um arquivo de cópias integrais dos rascunhos. | -| `opportunity_favourites` | PK `(user_id,opportunity_id)` | Favoritos privados, sem duplicações. Retirada de publicação oculta-os. | -| `investment_accounts` | PK/FK `user_id` | Perfil de investimento encriptado e versionado; complementa nome/email da conta. | -| `investment_company_declarations` | UUID; dono `user_id` | Dados empresariais declarados, encriptados e versionados; até vinte por utilizador. Não altera `organizations` ou `memberships`, nem comprova representação. | -| `investment_messages` | UUID; submissão/actor; chave única `(submission_id,actor_id,client_key)` | Conversa encriptada depois da submissão. Repetição com mesmo texto devolve ID; reutilização da chave com outro texto é conflito. | -| `investment_notifications` | UUID; utilizador/submissão; evento único por destinatário | Confirmação, estado e mensagem, conteúdo encriptado, data de leitura. Notificações internas, sem envio de email nesta extensão. | -| `investment_submission_revisions` | único `(submission_id,version)` | Snapshot encriptado do formulário, necessidades e IDs dos anexos em cada nova versão. Consulta por titular/gestor, sem restauro automático. | +| Tabela | Chave / relação | Conteúdo e regra | +| ----------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `investment_reference_options` | `id`; único `(group,code)` | Rótulo, actividade, ordem, obrigatoriedade de especificação. Códigos estáveis; desactivar não elimina referências históricas. | +| `opportunity_profiles` | PK/FK `opportunity_id` | Educação, modelo de negócio, requisitos, riscos, data-alvo, fonte, versão. Texto destinado à ficha pública após publicação. Não colocar aqui notas privadas. | +| `opportunity_clusters` | PK `(opportunity_id,cluster_id)` | Uma oportunidade pode pertencer a vários clusters. | +| `opportunity_corridors` | PK `(opportunity_id,corridor_id)` | Vários corredores por oportunidade. | +| `opportunity_projects` | PK `(opportunity_id,project_id)` | Vários projectos âncora por oportunidade. | +| `opportunity_locations` | PK `(opportunity_id,geo_location_id)` | Ligações às localizações existentes. Geometrias continuam em `geo_geometries`, PostGIS SRID 4326. | +| `opportunity_facets` | PK `(opportunity_id,option_id)` | Subsector, parceria, impacto, educação, infraestrutura, competências e necessidades configuráveis. | +| `investment_submissions` | UUID; dono `user_id`; recibo único `interest_request_id` | `payload` encriptado, versão, estado draft/submitted. No máximo um destino: oportunidade ou dossier; interesse geral admite nenhum. Submissão exige recibo e rascunho não o admite. | +| `investment_specific_needs` | `submission_id`; opções FK | Categoria, tipo, quantidade decimal não negativa, unidade, prioridade, prazo. Descrição, localização e demais detalhes são encriptados. | +| `investment_submission_attachments` | UUID; submissão/documento FK | Ficheiro privado em `documents`; nome, tipo, tamanho e checksum canónicos. | +| `investment_submission_events` | `submission_id`, actor, versão | Criação, gravação, anexo e submissão. Guarda actos e versões; não é um arquivo de cópias integrais dos rascunhos. | +| `opportunity_favourites` | PK `(user_id,opportunity_id)` | Favoritos privados, sem duplicações. Retirada de publicação oculta-os. | +| `investment_accounts` | PK/FK `user_id` | Perfil de investimento encriptado e versionado; complementa nome/email da conta. | +| `investment_company_declarations` | UUID; dono `user_id` | Dados empresariais declarados, encriptados e versionados; até vinte por utilizador. Não altera `organizations` ou `memberships`, nem comprova representação. | +| `investment_messages` | UUID; submissão/actor; chave única `(submission_id,actor_id,client_key)` | Conversa encriptada depois da submissão. Repetição com mesmo texto devolve ID; reutilização da chave com outro texto é conflito. | +| `investment_notifications` | UUID; utilizador/submissão; evento único por destinatário | Confirmação, estado e mensagem, conteúdo encriptado, data de leitura. Notificações internas, sem envio de email nesta extensão. | +| `investment_submission_revisions` | único `(submission_id,version)` | Snapshot encriptado do formulário, necessidades e IDs dos anexos em cada nova versão. Consulta por titular/gestor, sem restauro automático. | Relações principais: `users → investment_submissions → investment_specific_needs`; `investment_submissions → investment_submission_attachments → documents`; `investment_submissions → interest_requests → outbox_events → CRM`; `opportunities ↔ clusters/corridors/projects/locations` através dos quatro pivots. Dossiers continuam ligados às entidades territoriais originais, sem serem copiados para `opportunities`. @@ -76,17 +76,17 @@ Monitorizar erros HTTP 5xx, conflitos 409, recusas 403/404, limites 429, fila/ou ## Go / No-Go -| Item | Situação | -| --- | --- | -| Autorização dos requisitos | Expressamente concedida pelo proprietário; não requer nova aprovação genérica. | -| Extensão local e opções | Aplicadas após backup/restauro. | -| QA automatizado e navegador | Evidências em `validation.md`; conferir limitações de cada ensaio. | -| Conteúdo e geografia | Publica-se apenas o existente autorizado; completar dados com fontes reais. | -| Consolidação da outra máquina | Adiada pelo proprietário para fase futura; fora do âmbito da execução local actual. | -| WCAG 2.2 AA | Verificações parciais; falta auditoria completa com tecnologia assistiva. | +| Item | Situação | +| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| Autorização dos requisitos | Expressamente concedida pelo proprietário; não requer nova aprovação genérica. | +| Extensão local e opções | Aplicadas após backup/restauro. | +| QA automatizado e navegador | Evidências em `validation.md`; conferir limitações de cada ensaio. | +| Conteúdo e geografia | Publica-se apenas o existente autorizado; completar dados com fontes reais. | +| Consolidação da outra máquina | Adiada pelo proprietário para fase futura; fora do âmbito da execução local actual. | +| WCAG 2.2 AA | Verificações parciais; falta auditoria completa com tecnologia assistiva. | | Área pessoal, empresas declaradas, mensagens e notificações | Implementadas e testadas localmente; sem atribuição automática de representação empresarial. | -| Perfis especializados e cadastro territorial completo | Cobertura parcial; ver matriz de rastreabilidade. | -| SuiteCRM real, carga, recuperação no destino | Não ensaiados nesta entrega. | -| Formação e produção | Plano preparado; sessões e entrada em produção não executadas. | +| Perfis especializados e cadastro territorial completo | Cobertura parcial; ver matriz de rastreabilidade. | +| SuiteCRM real, carga, recuperação no destino | Não ensaiados nesta entrega. | +| Formação e produção | Plano preparado; sessões e entrada em produção não executadas. | Resultado: disponível para utilização e avaliação local. A conclusão integral do prompt e a prontidão de produção não estão demonstradas pelos testes desta extensão. diff --git a/docs/investmoz/unification/local-latency.json b/docs/investmoz/unification/local-latency.json index 4592efad..f5d18ab1 100644 --- a/docs/investmoz/unification/local-latency.json +++ b/docs/investmoz/unification/local-latency.json @@ -1,48 +1,30 @@ { - "date": "2026-09-27", - "environment": "local PHP development server, sequential loopback HTTP requests; no load or production SLA claim", - "measurements": [ - { - "path": "/api/v1/investment/search", - "repetitions": 5, - "median_ms": 170.67, - "min_ms": 154.9, - "max_ms": 207.66, - "samples_ms": [ - 207.66, - 154.9, - 168.66, - 198.91, - 170.67 - ] - }, - { - "path": "/api/v1/investment/references", - "repetitions": 5, - "median_ms": 173.39, - "min_ms": 151.81, - "max_ms": 210.99, - "samples_ms": [ - 210.99, - 184.29, - 162.7, - 173.39, - 151.81 - ] - }, - { - "path": "/api/v1/investment/map", - "repetitions": 5, - "median_ms": 160.86, - "min_ms": 148.68, - "max_ms": 198.9, - "samples_ms": [ - 172.47, - 160.11, - 198.9, - 160.86, - 148.68 - ] - } - ] -} \ No newline at end of file + "date": "2026-09-27", + "environment": "local PHP development server, sequential loopback HTTP requests; no load or production SLA claim", + "measurements": [ + { + "path": "/api/v1/investment/search", + "repetitions": 5, + "median_ms": 170.67, + "min_ms": 154.9, + "max_ms": 207.66, + "samples_ms": [207.66, 154.9, 168.66, 198.91, 170.67] + }, + { + "path": "/api/v1/investment/references", + "repetitions": 5, + "median_ms": 173.39, + "min_ms": 151.81, + "max_ms": 210.99, + "samples_ms": [210.99, 184.29, 162.7, 173.39, 151.81] + }, + { + "path": "/api/v1/investment/map", + "repetitions": 5, + "median_ms": 160.86, + "min_ms": 148.68, + "max_ms": 198.9, + "samples_ms": [172.47, 160.11, 198.9, 160.86, 148.68] + } + ] +} diff --git a/docs/investmoz/unification/openapi.json b/docs/investmoz/unification/openapi.json index 9be5f221..e34a72d1 100644 --- a/docs/investmoz/unification/openapi.json +++ b/docs/investmoz/unification/openapi.json @@ -1,4877 +1,4776 @@ { - "openapi": "3.0.3", - "info": { - "title": "InvestMoz — descoberta e manifestações", - "version": "1.0.0", - "description": "Contrato local 27/09/2026. Sessão web Laravel, email verificado e CSRF; não usa bearer independente. Accept: application/json. Respostas privadas no-store. Estados de atendimento: received,in_review,contacted,closed. API pública usa throttle api da aplicação; limites específicos indicados por operação. O middleware Laravel 13 aceita origem same-origin comprovada ou token CSRF válido; clientes devem enviar o token. Pedidos cross-site sem token são recusados. Pesquisa, referências, sugestão, mapa, comparação, exportação e favoritos partilham 60 pedidos/minuto por IP." - }, - "servers": [ - { - "url": "/api/v1/investment" - } - ], - "security": [], - "paths": { - "/search": { - "get": { - "summary": "Pesquisar oportunidades publicadas com filtros em intersecção.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Page" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "sector", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cluster_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "corridor_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "project_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "geo_location_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "education", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "maturity_level", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 6 - } - }, - { - "name": "facets", - "in": "query", - "schema": { - "type": "array", - "items": { - "type": "integer" - }, - "maxItems": 20 - } - }, - { - "name": "min_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "max_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "currency", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "MZN", - "USD", - "EUR", - "ZAR" - ] - } - }, - { - "name": "before", - "in": "query", - "schema": { - "type": "string", - "format": "date" - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "recent", - "title", - "amount_asc", - "amount_desc" - ] - } - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 12 - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - }, - { - "name": "zee_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "zfi_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "park_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "dimension", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "cluster", - "corridor", - "zee", - "zfi" - ] - }, - "description": "Require a visible association in the selected dimension, preserving other filters." - }, - { - "name": "view", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "grid", - "list", - "map", - "combined" - ] - }, - "description": "Presentation state retained in URL; does not alter permission checks." - } - ], - "description": "Min/max exigem currency; max >= min. Sem câmbio. Facetas: facets[]=1&facets[]=2. Ordenação monetária agrupada por moeda." - } + "openapi": "3.0.3", + "info": { + "title": "InvestMoz — descoberta e manifestações", + "version": "1.0.0", + "description": "Contrato local 27/09/2026. Sessão web Laravel, email verificado e CSRF; não usa bearer independente. Accept: application/json. Respostas privadas no-store. Estados de atendimento: received,in_review,contacted,closed. API pública usa throttle api da aplicação; limites específicos indicados por operação. O middleware Laravel 13 aceita origem same-origin comprovada ou token CSRF válido; clientes devem enviar o token. Pedidos cross-site sem token são recusados. Pesquisa, referências, sugestão, mapa, comparação, exportação e favoritos partilham 60 pedidos/minuto por IP." }, - "/references": { - "get": { - "summary": "Opções activas e territórios públicos.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/References" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/opportunities/{slug}": { - "get": { - "summary": "Ficha publicada e contexto.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Detail" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "slug", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ] - } - }, - "/suggest": { - "get": { - "summary": "Sugestões a partir de dois caracteres; até cinco de cada tipo.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "opportunities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - } - } - } - }, - "territorial": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "title": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "sector", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cluster_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "corridor_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "project_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "geo_location_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "education", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "maturity_level", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 6 - } - }, - { - "name": "facets", - "in": "query", - "schema": { - "type": "array", - "items": { - "type": "integer" - }, - "maxItems": 20 - } - }, - { - "name": "min_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "max_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "currency", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "MZN", - "USD", - "EUR", - "ZAR" - ] - } - }, - { - "name": "before", - "in": "query", - "schema": { - "type": "string", - "format": "date" - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "recent", - "title", - "amount_asc", - "amount_desc" - ] - } - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 12 - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - }, - { - "name": "zee_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "zfi_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "park_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "dimension", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "cluster", - "corridor", - "zee", - "zfi" - ] - }, - "description": "Require a visible association in the selected dimension, preserving other filters." - }, - { - "name": "view", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "grid", - "list", - "map", - "combined" - ] - }, - "description": "Presentation state retained in URL; does not alter permission checks." - } - ] - } - }, - "/map": { - "get": { - "summary": "Até mil pontos públicos WGS84, com filtros.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Map" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "sector", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cluster_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "corridor_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "project_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "geo_location_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "education", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "maturity_level", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 6 - } - }, - { - "name": "facets", - "in": "query", - "schema": { - "type": "array", - "items": { - "type": "integer" - }, - "maxItems": 20 - } - }, - { - "name": "min_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "max_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "currency", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "MZN", - "USD", - "EUR", - "ZAR" - ] - } - }, - { - "name": "before", - "in": "query", - "schema": { - "type": "string", - "format": "date" - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "recent", - "title", - "amount_asc", - "amount_desc" - ] - } - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 12 - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - }, - { - "name": "zee_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "zfi_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "park_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "dimension", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "cluster", - "corridor", - "zee", - "zfi" - ] - }, - "description": "Require a visible association in the selected dimension, preserving other filters." - }, - { - "name": "view", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "grid", - "list", - "map", - "combined" - ] - }, - "description": "Presentation state retained in URL; does not alter permission checks." - } - ] - } - }, - "/compare": { - "get": { - "summary": "Comparar até quatro oportunidades publicadas.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "records": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Detail" - } - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "ids", - "in": "query", - "required": true, - "description": "Enviar ids[]=1&ids[]=2", - "schema": { - "type": "array", - "items": { - "type": "integer" - }, - "minItems": 1, - "maxItems": 4, - "uniqueItems": true - } - } - ] - } - }, - "/export": { - "get": { - "summary": "CSV UTF-8 com BOM; até mil resultados; fórmulas neutralizadas.", - "responses": { - "200": { - "description": "CSV separado por ponto e vírgula", - "content": { - "text/csv": { - "schema": { - "type": "string" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "sector", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "district", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "cluster_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "corridor_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "project_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "geo_location_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "education", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "maturity_level", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 6 - } - }, - { - "name": "facets", - "in": "query", - "schema": { - "type": "array", - "items": { - "type": "integer" - }, - "maxItems": 20 - } - }, - { - "name": "min_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "max_amount", - "in": "query", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "currency", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "MZN", - "USD", - "EUR", - "ZAR" - ] - } - }, - { - "name": "before", - "in": "query", - "schema": { - "type": "string", - "format": "date" - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "recent", - "title", - "amount_asc", - "amount_desc" - ] - } - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 12 - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - }, - { - "name": "zee_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "zfi_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "park_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Current validated public association. AND with other filters." - }, - { - "name": "dimension", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "cluster", - "corridor", - "zee", - "zfi" - ] - }, - "description": "Require a visible association in the selected dimension, preserving other filters." - }, - { - "name": "view", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "grid", - "list", - "map", - "combined" - ] - }, - "description": "Presentation state retained in URL; does not alter permission checks." - } - ] - } - }, - "/favourites/{id}": { - "post": { - "summary": "Guardar/retirar favorito próprio.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "saved": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "saved": { - "type": "boolean" - } - }, - "required": [ - "saved" - ] - } - } - } - } - } - }, - "/submissions": { - "get": { - "summary": "Manifestações próprias (20/página) e favoritos publicados.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "submissions": { - "type": "object", - "properties": {}, - "description": "Paginator Laravel: data, total, current_page, next_page_url, prev_page_url; linhas com id,status,version,created_at,reference,request_status." - }, - "favourites": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Opportunity" - } - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [] - } - ], - "parameters": [ - { - "name": "page", - "in": "query", - "schema": { - "type": "integer" - } - } - ] - }, - "post": { - "summary": "Criar rascunho próprio; no máximo um destino publicado; 10/min.", - "responses": { - "201": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "opportunity_id": { - "type": "integer", - "nullable": true - }, - "dossier_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "company_id": { - "type": "string", - "format": "uuid", - "nullable": true, - "description": "Declaração de empresa do próprio utilizador, usada para pré-preenchimento." - }, - "zone_id": { - "type": "string", - "format": "uuid", - "nullable": true, - "description": "Published zone origin; may combine with a publicly associated opportunity, not with dossier_id or company_id." - }, - "intent": { - "type": "string", - "enum": [ - "interest", - "dossier", - "meeting" - ], - "description": "Required with zone_id. Reuses the existing draft for this user, zone version, opportunity and intent." - } - } - } - } - } - } - } - }, - "/submissions/{id}": { - "parameters": [ + "servers": [ { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } + "url": "/api/v1/investment" } - ], - "get": { - "summary": "Consultar pedido próprio ou, com manage-portal, pedido em atendimento.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "submission": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - }, - "status": { - "type": "string" - }, - "payload": { - "$ref": "#/components/schemas/Payload" + ], + "security": [], + "paths": { + "/search": { + "get": { + "summary": "Pesquisar oportunidades publicadas com filtros em intersecção.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page" + } + } } - } }, - "needs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Need" - } + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "references": { - "$ref": "#/components/schemas/References" + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "attachments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "sector", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cluster_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" - }, - "file_name": { - "type": "string" - } } - } }, - "events": { - "type": "array", - "items": { - "type": "object", - "properties": { - "action": { - "type": "string" - }, - "version": { - "type": "integer" - }, - "created_at": { - "type": "string", - "format": "date-time" - } - } - } - }, - "receipt": { - "type": "object", - "properties": { - "reference": { - "type": "string" - }, - "status": { - "type": "string" - } - }, - "nullable": true - }, - "messages": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { + { + "name": "corridor_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" - }, - "sender_role": { + } + }, + { + "name": "project_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "geo_location_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "education", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "maturity_level", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 6 + } + }, + { + "name": "facets", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 20 + } + }, + { + "name": "min_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "max_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": "string", + "enum": ["MZN", "USD", "EUR", "ZAR"] + } + }, + { + "name": "before", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "sort", + "in": "query", + "schema": { "type": "string", "enum": [ - "investor", - "support" + "recent", + "title", + "amount_asc", + "amount_desc" ] - }, - "body": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - } } - } }, - "revisions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "version": { - "type": "integer" - }, - "created_at": { - "type": "string", - "format": "date-time" - } + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 12 } - } - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [] - } - ] - }, - "put": { - "summary": "Guardar rascunho próprio com versão optimista; 30/min.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Draft" - } - } - } - } - } - }, - "/submissions/{id}/submit": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "post": { - "summary": "Submeter atomicamente; reenvio devolve protocolo e estado existentes; 10/min.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "reference": { - "type": "string" }, - "status": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Draft" - } - } - } - }, - "description": "Somente titular. Exige campos finais e consentimento. Um recibo e uma outbox de CRM; sem activação de conector externo." - } - }, - "/submissions/{id}/attachments": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "post": { - "summary": "Carregar anexo privado em rascunho; 10/min.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "version": { - "type": "integer" - }, - "file": { - "type": "string", - "format": "binary", - "description": "PDF/TXT até 10 MiB; máximo dez anexos." - } - }, - "required": [ - "version", - "file" - ] - } - } - } - } - } - }, - "/submissions/{id}/attachments/{attachment}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "attachment", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "get": { - "summary": "Download privado após validar titular/gestor, caminho, estado e checksum.", - "responses": { - "200": { - "description": "Attachment; no-store, nosniff", - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [] - } - ] - } - }, - "/admin/opportunities/{id}/context": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "put": { - "summary": "Gestor altera contexto; devolve oportunidade a rascunho.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContextInput" - } - } - } - }, - "description": "manage-portal obrigatório. version=0 antes da criação do perfil. Opções activas, entidades não eliminadas. Não publica." - } - }, - "/admin/references": { - "post": { - "summary": "Gestor cria/actualiza opção; grupo e código estáveis.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Option" - } - } - } - }, - "description": "manage-portal obrigatório. Grupo existente; Outro mantém actividade e requires_description. Sem eliminação histórica." - } - }, - "/admin/references/{id}": { - "post": { - "summary": "Gestor cria/actualiza opção; grupo e código estáveis.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Option" - } - } - } - }, - "description": "manage-portal obrigatório. Grupo existente; Outro mantém actividade e requires_description. Sem eliminação histórica." - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ] - }, - "/profile": { - "get": { - "summary": "Perfil privado e empresas declaradas do titular.", - "security": [ - { - "session": [] - } - ], - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "account": { - "$ref": "#/components/schemas/InvestorAccount" + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } }, - "companies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CompanyDeclaration" - } - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "put": { - "summary": "Guardar perfil privado com versão; 20/min.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvestorAccount" - } - } - } - }, - "description": "version=0 inicialmente. Campos desconhecidos recusados; não altera nome/email nem concede representação de empresa.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/companies": { - "post": { - "summary": "Guardar declaração privada de empresa; 20/min.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompanyDeclaration" - } - } - } - }, - "description": "Titular apenas. Máximo vinte declarações; versão zero para criar. Não cria memberships nem altera organizações canónicas.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/companies/{id}": { - "post": { - "summary": "Guardar declaração privada de empresa; 20/min.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompanyDeclaration" - } - } - } - }, - "description": "Titular apenas. Máximo vinte declarações; versão zero para criar. Não cria memberships nem altera organizações canónicas.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ] - }, - "/notifications": { - "get": { - "summary": "Notificações próprias, vinte por página, e contagem não lida.", - "security": [ - { - "session": [] - } - ], - "parameters": [ - { - "name": "page", - "in": "query", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "unread": { - "type": "integer" - }, - "notifications": { - "type": "object", - "properties": {}, - "description": "Paginator Laravel com data: id,submission_id,kind,payload,read_at,created_at. Tipos received,status,message. Sem email externo." - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/notifications/{id}/read": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "post": { - "summary": "Marcar notificação própria como lida; idempotente.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "read": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/submissions/{id}/messages": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "post": { - "summary": "Mensagem privada em pedido submetido; 10/min.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "client_key": { - "type": "string", - "format": "uuid" - }, - "body": { - "type": "string", - "minLength": 3, - "maxLength": 5000 - } - }, - "required": [ - "client_key", - "body" - ] - } - } - } - }, - "description": "Titular ou gestor. Mesma chave e texto devolvem a mensagem existente. Chave reutilizada com texto diferente:409. Texto encriptado; auditoria sem corpo. Cria notificações internas para a outra parte.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/submissions/{id}/revisions/{version}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "get": { - "summary": "Consultar versão histórica encriptada do pedido.", - "security": [ - { - "session": [] - } - ], - "description": "Titular ou gestor actual. Só versões gravadas após esta extensão. Não altera nem restaura automaticamente o pedido; no-store.", - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "version": { - "type": "integer" - }, - "snapshot": { - "type": "object", - "properties": { - "payload": { - "$ref": "#/components/schemas/Payload" - }, - "status": { - "type": "string" - }, - "needs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Need" - } - }, - "attachment_ids": { - "type": "array", - "items": { + { + "name": "zee_id", + "in": "query", + "schema": { "type": "string", "format": "uuid" - } - } - } + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "zfi_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "park_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "dimension", + "in": "query", + "schema": { + "type": "string", + "enum": ["cluster", "corridor", "zee", "zfi"] + }, + "description": "Require a visible association in the selected dimension, preserving other filters." + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": ["grid", "list", "map", "combined"] + }, + "description": "Presentation state retained in URL; does not alter permission checks." } - } - } - } + ], + "description": "Min/max exigem currency; max >= min. Sem câmbio. Facetas: facets[]=1&facets[]=2. Ordenação monetária agrupada por moeda." } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/references": { + "get": { + "summary": "Opções activas e territórios públicos.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/References" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } - } } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/opportunities/{slug}": { + "get": { + "summary": "Ficha publicada e contexto.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/suggest": { + "get": { + "summary": "Sugestões a partir de dois caracteres; até cinco de cada tipo.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "opportunities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + } + }, + "territorial": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "sector", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cluster_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "corridor_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "project_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "geo_location_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "education", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "maturity_level", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 6 + } + }, + { + "name": "facets", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 20 + } + }, + { + "name": "min_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "max_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": "string", + "enum": ["MZN", "USD", "EUR", "ZAR"] + } + }, + { + "name": "before", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "recent", + "title", + "amount_asc", + "amount_desc" + ] + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 12 + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "zee_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "zfi_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "park_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "dimension", + "in": "query", + "schema": { + "type": "string", + "enum": ["cluster", "corridor", "zee", "zfi"] + }, + "description": "Require a visible association in the selected dimension, preserving other filters." + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": ["grid", "list", "map", "combined"] + }, + "description": "Presentation state retained in URL; does not alter permission checks." + } + ] + } + }, + "/map": { + "get": { + "summary": "Até mil pontos públicos WGS84, com filtros.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Map" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "sector", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cluster_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "corridor_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "project_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "geo_location_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "education", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "maturity_level", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 6 + } + }, + { + "name": "facets", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 20 + } + }, + { + "name": "min_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "max_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": "string", + "enum": ["MZN", "USD", "EUR", "ZAR"] + } + }, + { + "name": "before", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "recent", + "title", + "amount_asc", + "amount_desc" + ] + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 12 + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "zee_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "zfi_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "park_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "dimension", + "in": "query", + "schema": { + "type": "string", + "enum": ["cluster", "corridor", "zee", "zfi"] + }, + "description": "Require a visible association in the selected dimension, preserving other filters." + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": ["grid", "list", "map", "combined"] + }, + "description": "Presentation state retained in URL; does not alter permission checks." + } + ] + } + }, + "/compare": { + "get": { + "summary": "Comparar até quatro oportunidades publicadas.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Detail" + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "ids", + "in": "query", + "required": true, + "description": "Enviar ids[]=1&ids[]=2", + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "maxItems": 4, + "uniqueItems": true + } + } + ] + } + }, + "/export": { + "get": { + "summary": "CSV UTF-8 com BOM; até mil resultados; fórmulas neutralizadas.", + "responses": { + "200": { + "description": "CSV separado por ponto e vírgula", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "sector", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "district", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cluster_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "corridor_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "project_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "geo_location_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "education", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "maturity_level", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 6 + } + }, + { + "name": "facets", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 20 + } + }, + { + "name": "min_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "max_amount", + "in": "query", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": "string", + "enum": ["MZN", "USD", "EUR", "ZAR"] + } + }, + { + "name": "before", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "recent", + "title", + "amount_asc", + "amount_desc" + ] + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 12 + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "zee_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "zfi_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "park_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Current validated public association. AND with other filters." + }, + { + "name": "dimension", + "in": "query", + "schema": { + "type": "string", + "enum": ["cluster", "corridor", "zee", "zfi"] + }, + "description": "Require a visible association in the selected dimension, preserving other filters." + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": ["grid", "list", "map", "combined"] + }, + "description": "Presentation state retained in URL; does not alter permission checks." + } + ] + } + }, + "/favourites/{id}": { + "post": { + "summary": "Guardar/retirar favorito próprio.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "saved": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "saved": { + "type": "boolean" + } + }, + "required": ["saved"] + } + } + } } - } } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/submissions": { + "get": { + "summary": "Manifestações próprias (20/página) e favoritos publicados.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "submissions": { + "type": "object", + "properties": {}, + "description": "Paginator Laravel: data, total, current_page, next_page_url, prev_page_url; linhas com id,status,version,created_at,reference,request_status." + }, + "favourites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Opportunity" + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer" + } + } + ] + }, + "post": { + "summary": "Criar rascunho próprio; no máximo um destino publicado; 10/min.", + "responses": { + "201": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "opportunity_id": { + "type": "integer", + "nullable": true + }, + "dossier_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "company_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Declaração de empresa do próprio utilizador, usada para pré-preenchimento." + }, + "zone_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Published zone origin; may combine with a publicly associated opportunity, not with dossier_id or company_id." + }, + "intent": { + "type": "string", + "enum": [ + "interest", + "dossier", + "meeting" + ], + "description": "Required with zone_id. Reuses the existing draft for this user, zone version, opportunity and intent." + } + } + } + } + } } - } } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/submissions/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + ], + "get": { + "summary": "Consultar pedido próprio ou, com manage-portal, pedido em atendimento.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "submission": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/Payload" + } + } + }, + "needs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Need" + } + }, + "references": { + "$ref": "#/components/schemas/References" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "file_name": { + "type": "string" + } + } + } + }, + "events": { + "type": "array", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "receipt": { + "type": "object", + "properties": { + "reference": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "nullable": true + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "sender_role": { + "type": "string", + "enum": [ + "investor", + "support" + ] + }, + "body": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "revisions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [] + } + ] + }, + "put": { + "summary": "Guardar rascunho próprio com versão optimista; 30/min.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Draft" + } + } + } } - } } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/submissions/{id}/submit": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } } - } + ], + "post": { + "summary": "Submeter atomicamente; reenvio devolve protocolo e estado existentes; 10/min.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reference": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Draft" + } + } + } + }, + "description": "Somente titular. Exige campos finais e consentimento. Um recibo e uma outbox de CRM; sem activação de conector externo." } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + }, + "/submissions/{id}/attachments": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "post": { + "summary": "Carregar anexo privado em rascunho; 10/min.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "file": { + "type": "string", + "format": "binary", + "description": "PDF/TXT até 10 MiB; máximo dez anexos." + } + }, + "required": ["version", "file"] + } + } + } } - } } - } + }, + "/submissions/{id}/attachments/{attachment}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "attachment", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "get": { + "summary": "Download privado após validar titular/gestor, caminho, estado e checksum.", + "responses": { + "200": { + "description": "Attachment; no-store, nosniff", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [] + } + ] + } + }, + "/admin/opportunities/{id}/context": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "put": { + "summary": "Gestor altera contexto; devolve oportunidade a rascunho.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextInput" + } + } + } + }, + "description": "manage-portal obrigatório. version=0 antes da criação do perfil. Opções activas, entidades não eliminadas. Não publica." + } + }, + "/admin/references": { + "post": { + "summary": "Gestor cria/actualiza opção; grupo e código estáveis.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Option" + } + } + } + }, + "description": "manage-portal obrigatório. Grupo existente; Outro mantém actividade e requires_description. Sem eliminação histórica." + } + }, + "/admin/references/{id}": { + "post": { + "summary": "Gestor cria/actualiza opção; grupo e código estáveis.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Option" + } + } + } + }, + "description": "manage-portal obrigatório. Grupo existente; Outro mantém actividade e requires_description. Sem eliminação histórica." + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] + }, + "/profile": { + "get": { + "summary": "Perfil privado e empresas declaradas do titular.", + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "account": { + "$ref": "#/components/schemas/InvestorAccount" + }, + "companies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CompanyDeclaration" + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "summary": "Guardar perfil privado com versão; 20/min.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestorAccount" + } + } + } + }, + "description": "version=0 inicialmente. Campos desconhecidos recusados; não altera nome/email nem concede representação de empresa.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/companies": { + "post": { + "summary": "Guardar declaração privada de empresa; 20/min.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompanyDeclaration" + } + } + } + }, + "description": "Titular apenas. Máximo vinte declarações; versão zero para criar. Não cria memberships nem altera organizações canónicas.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/companies/{id}": { + "post": { + "summary": "Guardar declaração privada de empresa; 20/min.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompanyDeclaration" + } + } + } + }, + "description": "Titular apenas. Máximo vinte declarações; versão zero para criar. Não cria memberships nem altera organizações canónicas.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ] + }, + "/notifications": { + "get": { + "summary": "Notificações próprias, vinte por página, e contagem não lida.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "unread": { + "type": "integer" + }, + "notifications": { + "type": "object", + "properties": {}, + "description": "Paginator Laravel com data: id,submission_id,kind,payload,read_at,created_at. Tipos received,status,message. Sem email externo." + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/notifications/{id}/read": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "post": { + "summary": "Marcar notificação própria como lida; idempotente.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "read": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/submissions/{id}/messages": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "post": { + "summary": "Mensagem privada em pedido submetido; 10/min.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "client_key": { + "type": "string", + "format": "uuid" + }, + "body": { + "type": "string", + "minLength": 3, + "maxLength": 5000 + } + }, + "required": ["client_key", "body"] + } + } + } + }, + "description": "Titular ou gestor. Mesma chave e texto devolvem a mensagem existente. Chave reutilizada com texto diferente:409. Texto encriptado; auditoria sem corpo. Cria notificações internas para a outra parte.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/submissions/{id}/revisions/{version}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "get": { + "summary": "Consultar versão histórica encriptada do pedido.", + "security": [ + { + "session": [] + } + ], + "description": "Titular ou gestor actual. Só versões gravadas após esta extensão. Não altera nem restaura automaticamente o pedido; no-store.", + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "snapshot": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/Payload" + }, + "status": { + "type": "string" + }, + "needs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Need" + } + }, + "attachment_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/admin/interests/{interest}/status": { + "parameters": [ + { + "name": "interest", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "patch": { + "summary": "Gestor actualiza estado de atendimento e notifica o titular quando muda.", + "security": [ + { + "session": [], + "csrf": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "type": "string", + "enum": [ + "received", + "in_review", + "contacted", + "closed" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reference": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Sem sessão", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Email não verificado / sem manage-portal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Registo ausente ou invisível", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Versão desactualizada / pedido submetido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "419": { + "description": "Sessão / CSRF expirado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validação", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Limite de pedidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "description": "Exige manage-portal. Alteração transaccional com auditoria. Repetir o mesmo estado não cria outra notificação." + } } - } }, - "/admin/interests/{interest}/status": { - "parameters": [ - { - "name": "interest", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "patch": { - "summary": "Gestor actualiza estado de atendimento e notifica o titular quando muda.", - "security": [ - { - "session": [], - "csrf": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { + "components": { + "securitySchemes": { + "session": { + "type": "apiKey", + "in": "cookie", + "name": "laravel_session", + "description": "Nome configurável SESSION_COOKIE; autenticar pelo fluxo web." + }, + "csrf": { + "type": "apiKey", + "in": "header", + "name": "X-XSRF-TOKEN", + "description": "Valor descodificado do cookie XSRF-TOKEN." + } + }, + "schemas": { + "Error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "Version": { + "type": "object", + "properties": { + "version": { + "type": "integer" + } + } + }, + "Entity": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "Option": { "type": "object", "required": [ - "status" + "group", + "code", + "label", + "active", + "requires_description", + "sort_order" ], "properties": { - "status": { - "type": "string", - "enum": [ - "received", - "in_review", - "contacted", - "closed" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Sucesso", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "reference": { - "type": "string" - }, - "status": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "Sem sessão", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Email não verificado / sem manage-portal", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Registo ausente ou invisível", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "409": { - "description": "Versão desactualizada / pedido submetido", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "419": { - "description": "Sessão / CSRF expirado", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validação", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "description": "Limite de pedidos", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "description": "Exige manage-portal. Alteração transaccional com auditoria. Repetir o mesmo estado não cria outra notificação." - } - } - }, - "components": { - "securitySchemes": { - "session": { - "type": "apiKey", - "in": "cookie", - "name": "laravel_session", - "description": "Nome configurável SESSION_COOKIE; autenticar pelo fluxo web." - }, - "csrf": { - "type": "apiKey", - "in": "header", - "name": "X-XSRF-TOKEN", - "description": "Valor descodificado do cookie XSRF-TOKEN." - } - }, - "schemas": { - "Error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "errors": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "Version": { - "type": "object", - "properties": { - "version": { - "type": "integer" - } - } - }, - "Entity": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "name": { - "type": "string" - } - } - }, - "Option": { - "type": "object", - "required": [ - "group", - "code", - "label", - "active", - "requires_description", - "sort_order" - ], - "properties": { - "id": { - "type": "integer" - }, - "group": { - "type": "string" - }, - "code": { - "type": "string", - "maxLength": 80, - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "label": { - "type": "string", - "maxLength": 200 - }, - "active": { - "type": "boolean" - }, - "requires_description": { - "type": "boolean" - }, - "sort_order": { - "type": "integer", - "minimum": 0, - "maximum": 10000 - } - } - }, - "Payload": { - "type": "object", - "additionalProperties": false, - "properties": { - "full_name": { - "type": "string", - "nullable": true, - "maxLength": 150 - }, - "email": { - "type": "string", - "nullable": true, - "maxLength": 150 - }, - "phone": { - "type": "string", - "nullable": true - }, - "country": { - "type": "string", - "nullable": true - }, - "organisation": { - "type": "string", - "nullable": true - }, - "representative_role": { - "type": "string", - "nullable": true - }, - "experience": { - "type": "string", - "nullable": true - }, - "investment_capacity": { - "type": "string", - "nullable": true - }, - "sector": { - "type": "string", - "nullable": true - }, - "objective": { - "type": "string", - "maxLength": 5000, - "nullable": true - }, - "currency": { - "type": "string", - "nullable": true - }, - "horizon": { - "type": "string", - "nullable": true - }, - "location": { - "type": "string", - "nullable": true - }, - "other_details": { - "type": "string", - "nullable": true - }, - "contact_consent": { - "type": "boolean" - }, - "estimated_amount": { - "type": "number", - "minimum": 0, - "nullable": true - }, - "profile_id": { - "type": "integer", - "nullable": true - }, - "participation_id": { - "type": "integer", - "nullable": true - }, - "partnership_id": { - "type": "integer", - "nullable": true - }, - "cluster_ids": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "maxItems": 20, - "uniqueItems": true - }, - "corridor_ids": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "maxItems": 20, - "uniqueItems": true - }, - "project_ids": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "maxItems": 30, - "uniqueItems": true - } - }, - "description": "Para submeter: full_name, email, country, profile_id, sector, objective, participation_id e horizon obrigatórios; contact_consent=true. Moeda obrigatória com montante. Outro exige other_details. Opções activas do grupo correcto. Drafts permit incomplete name, email and objective. Submission requires a name of at least 3 characters, RFC email and objective of at least 20 characters." - }, - "Draft": { - "type": "object", - "required": [ - "version", - "payload" - ], - "properties": { - "version": { - "type": "integer", - "minimum": 1 - }, - "payload": { - "$ref": "#/components/schemas/Payload" - }, - "needs": { - "type": "array", - "maxItems": 40, - "items": { - "$ref": "#/components/schemas/Need" - } - } - } - }, - "Need": { - "type": "object", - "additionalProperties": false, - "required": [ - "category_id", - "option_id", - "details" - ], - "properties": { - "category_id": { - "type": "integer" - }, - "option_id": { - "type": "integer" - }, - "unit_id": { - "type": "integer", - "nullable": true - }, - "priority_id": { - "type": "integer", - "nullable": true - }, - "quantity": { - "type": "number", - "nullable": true, - "minimum": 0, - "maximum": 999999999999 - }, - "deadline": { - "type": "string", - "format": "date", - "nullable": true - }, - "details": { - "type": "object", - "additionalProperties": false, - "required": [ - "description" - ], - "properties": { - "description": { - "type": "string", - "nullable": false, - "maxLength": 2000, - "minLength": 3 - }, - "location": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "observations": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "qualification": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "specialty": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "experience": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "certifications": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "availability": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "institution": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "training_mode": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "duration": { - "type": "string", - "nullable": true, - "maxLength": 2000 - }, - "status": { - "type": "string", - "nullable": true, - "maxLength": 2000 - } - } - } - } - }, - "Opportunity": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "reference": { - "type": "string" - }, - "summary": { - "type": "string", - "nullable": true - }, - "province": { - "type": "string", - "nullable": true - }, - "district": { - "type": "string", - "nullable": true - }, - "estimated_investment_amount": { - "type": "string", - "nullable": true, - "example": "1500000.00" - }, - "estimated_investment_currency": { - "type": "string" - }, - "maturity_label": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string" - }, - "sector": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - } - } - } - } - }, - "Page": { - "type": "object", - "properties": { - "current_page": { - "type": "integer" - }, - "per_page": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "last_page": { - "type": "integer" - }, - "next_page_url": { - "type": "string", - "nullable": true - }, - "prev_page_url": { - "type": "string", - "nullable": true - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Opportunity" - } - } - } - }, - "ContextInput": { - "type": "object", - "required": [ - "version", - "is_education", - "source_reference" - ], - "properties": { - "version": { - "type": "integer", - "minimum": 0 - }, - "is_education": { - "type": "boolean" - }, - "source_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "business_model": { - "type": "string", - "maxLength": 5000, - "nullable": true - }, - "requirements": { - "type": "string", - "maxLength": 10000, - "nullable": true - }, - "risks": { - "type": "string", - "maxLength": 5000, - "nullable": true - }, - "target_date": { - "type": "string", - "format": "date", - "nullable": true - }, - "facets": { - "type": "array", - "maxItems": 100, - "items": { - "type": "integer" - }, - "uniqueItems": true - }, - "clusters": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "corridors": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "projects": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "locations": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - } - } - }, - "Context": { - "type": "object", - "properties": { - "profile": { - "type": "object", - "nullable": true, - "properties": { - "version": { - "type": "integer", - "minimum": 0 - }, - "is_education": { - "type": "boolean" - }, - "source_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "business_model": { - "type": "string", - "maxLength": 5000, - "nullable": true - }, - "requirements": { - "type": "string", - "maxLength": 10000, - "nullable": true - }, - "risks": { - "type": "string", - "maxLength": 5000, - "nullable": true - }, - "target_date": { - "type": "string", - "format": "date", - "nullable": true - }, - "facets": { - "type": "array", - "maxItems": 100, - "items": { - "type": "integer" - }, - "uniqueItems": true - }, - "clusters": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "corridors": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "projects": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - }, - "locations": { - "type": "array", - "maxItems": 100, - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true - } - } - }, - "facets": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "group": { - "type": "string" - }, - "label": { - "type": "string" - } - } - } - }, - "clusters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "corridors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "projects": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "locations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - } - } - }, - "References": { - "type": "object", - "properties": { - "options": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Option" - } - }, - "sectors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - } - } - } - }, - "clusters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "corridors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "projects": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - }, - "locations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } - } - } - }, - "Detail": { - "type": "object", - "properties": { - "record": { - "$ref": "#/components/schemas/Opportunity" - }, - "context": { - "$ref": "#/components/schemas/Context" - } - } - }, - "Map": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "FeatureCollection" - ] - }, - "limit": { - "type": "integer", - "enum": [ - 1000 - ] - }, - "features": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "Feature" - ] - }, - "geometry": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "Point" - ] - }, - "coordinates": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "items": { - "type": "number" - } - } - } - }, - "properties": { - "type": "object", - "properties": { "id": { - "type": "integer" + "type": "integer" }, - "title": { - "type": "string" + "group": { + "type": "string" + }, + "code": { + "type": "string", + "maxLength": 80, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "label": { + "type": "string", + "maxLength": 200 + }, + "active": { + "type": "boolean" + }, + "requires_description": { + "type": "boolean" + }, + "sort_order": { + "type": "integer", + "minimum": 0, + "maximum": 10000 + } + } + }, + "Payload": { + "type": "object", + "additionalProperties": false, + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 150 + }, + "email": { + "type": "string", + "nullable": true, + "maxLength": 150 + }, + "phone": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "organisation": { + "type": "string", + "nullable": true + }, + "representative_role": { + "type": "string", + "nullable": true + }, + "experience": { + "type": "string", + "nullable": true + }, + "investment_capacity": { + "type": "string", + "nullable": true + }, + "sector": { + "type": "string", + "nullable": true + }, + "objective": { + "type": "string", + "maxLength": 5000, + "nullable": true + }, + "currency": { + "type": "string", + "nullable": true + }, + "horizon": { + "type": "string", + "nullable": true }, "location": { - "type": "string" + "type": "string", + "nullable": true }, - "url": { - "type": "string" + "other_details": { + "type": "string", + "nullable": true + }, + "contact_consent": { + "type": "boolean" + }, + "estimated_amount": { + "type": "number", + "minimum": 0, + "nullable": true + }, + "profile_id": { + "type": "integer", + "nullable": true + }, + "participation_id": { + "type": "integer", + "nullable": true + }, + "partnership_id": { + "type": "integer", + "nullable": true + }, + "cluster_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 20, + "uniqueItems": true + }, + "corridor_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 20, + "uniqueItems": true + }, + "project_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 30, + "uniqueItems": true } - } - } - } - } - } - } - }, - "InvestorAccount": { - "type": "object", - "properties": { - "version": { - "type": "integer" - }, - "payload": { - "type": "object", - "properties": { - "phone": { - "type": "string", - "nullable": true - }, - "country": { - "type": "string", - "nullable": true - }, - "sector": { - "type": "string", - "nullable": true - }, - "experience": { - "type": "string", - "nullable": true - }, - "investment_capacity": { - "type": "string", - "nullable": true - } - } - } - }, - "required": [ - "version", - "payload" - ] - }, - "CompanyDeclaration": { - "type": "object", - "properties": { - "version": { - "type": "integer" - }, - "payload": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "country": { - "type": "string" - }, - "registration": { - "type": "string" - }, - "sector": { - "type": "string" - }, - "role": { - "type": "string" - }, - "description": { - "type": "string" - } + }, + "description": "Para submeter: full_name, email, country, profile_id, sector, objective, participation_id e horizon obrigatórios; contact_consent=true. Moeda obrigatória com montante. Outro exige other_details. Opções activas do grupo correcto. Drafts permit incomplete name, email and objective. Submission requires a name of at least 3 characters, RFC email and objective of at least 20 characters." }, - "required": [ - "name", - "country", - "role" - ], - "additionalProperties": false - } - }, - "required": [ - "version", - "payload" - ] - } + "Draft": { + "type": "object", + "required": ["version", "payload"], + "properties": { + "version": { + "type": "integer", + "minimum": 1 + }, + "payload": { + "$ref": "#/components/schemas/Payload" + }, + "needs": { + "type": "array", + "maxItems": 40, + "items": { + "$ref": "#/components/schemas/Need" + } + } + } + }, + "Need": { + "type": "object", + "additionalProperties": false, + "required": ["category_id", "option_id", "details"], + "properties": { + "category_id": { + "type": "integer" + }, + "option_id": { + "type": "integer" + }, + "unit_id": { + "type": "integer", + "nullable": true + }, + "priority_id": { + "type": "integer", + "nullable": true + }, + "quantity": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 999999999999 + }, + "deadline": { + "type": "string", + "format": "date", + "nullable": true + }, + "details": { + "type": "object", + "additionalProperties": false, + "required": ["description"], + "properties": { + "description": { + "type": "string", + "nullable": false, + "maxLength": 2000, + "minLength": 3 + }, + "location": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "observations": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "qualification": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "specialty": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "experience": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "certifications": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "availability": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "institution": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "training_mode": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "duration": { + "type": "string", + "nullable": true, + "maxLength": 2000 + }, + "status": { + "type": "string", + "nullable": true, + "maxLength": 2000 + } + } + } + } + }, + "Opportunity": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "summary": { + "type": "string", + "nullable": true + }, + "province": { + "type": "string", + "nullable": true + }, + "district": { + "type": "string", + "nullable": true + }, + "estimated_investment_amount": { + "type": "string", + "nullable": true, + "example": "1500000.00" + }, + "estimated_investment_currency": { + "type": "string" + }, + "maturity_label": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "sector": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + } + } + }, + "Page": { + "type": "object", + "properties": { + "current_page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "last_page": { + "type": "integer" + }, + "next_page_url": { + "type": "string", + "nullable": true + }, + "prev_page_url": { + "type": "string", + "nullable": true + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Opportunity" + } + } + } + }, + "ContextInput": { + "type": "object", + "required": ["version", "is_education", "source_reference"], + "properties": { + "version": { + "type": "integer", + "minimum": 0 + }, + "is_education": { + "type": "boolean" + }, + "source_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "business_model": { + "type": "string", + "maxLength": 5000, + "nullable": true + }, + "requirements": { + "type": "string", + "maxLength": 10000, + "nullable": true + }, + "risks": { + "type": "string", + "maxLength": 5000, + "nullable": true + }, + "target_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "facets": { + "type": "array", + "maxItems": 100, + "items": { + "type": "integer" + }, + "uniqueItems": true + }, + "clusters": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "corridors": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "projects": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "locations": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + } + } + }, + "Context": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "nullable": true, + "properties": { + "version": { + "type": "integer", + "minimum": 0 + }, + "is_education": { + "type": "boolean" + }, + "source_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "business_model": { + "type": "string", + "maxLength": 5000, + "nullable": true + }, + "requirements": { + "type": "string", + "maxLength": 10000, + "nullable": true + }, + "risks": { + "type": "string", + "maxLength": 5000, + "nullable": true + }, + "target_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "facets": { + "type": "array", + "maxItems": 100, + "items": { + "type": "integer" + }, + "uniqueItems": true + }, + "clusters": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "corridors": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "projects": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + }, + "locations": { + "type": "array", + "maxItems": 100, + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true + } + } + }, + "facets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "label": { + "type": "string" + } + } + } + }, + "clusters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "corridors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "locations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "References": { + "type": "object", + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Option" + } + }, + "sectors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + } + }, + "clusters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "corridors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + }, + "locations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "Detail": { + "type": "object", + "properties": { + "record": { + "$ref": "#/components/schemas/Opportunity" + }, + "context": { + "$ref": "#/components/schemas/Context" + } + } + }, + "Map": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["FeatureCollection"] + }, + "limit": { + "type": "integer", + "enum": [1000] + }, + "features": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Feature"] + }, + "geometry": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "location": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + } + } + } + } + }, + "InvestorAccount": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "payload": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "sector": { + "type": "string", + "nullable": true + }, + "experience": { + "type": "string", + "nullable": true + }, + "investment_capacity": { + "type": "string", + "nullable": true + } + } + } + }, + "required": ["version", "payload"] + }, + "CompanyDeclaration": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "payload": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "country": { + "type": "string" + }, + "registration": { + "type": "string" + }, + "sector": { + "type": "string" + }, + "role": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": ["name", "country", "role"], + "additionalProperties": false + } + }, + "required": ["version", "payload"] + } + } } - } } diff --git a/docs/investmoz/unification/public-browser-validation.json b/docs/investmoz/unification/public-browser-validation.json index a9a233a9..02d8eefb 100644 --- a/docs/investmoz/unification/public-browser-validation.json +++ b/docs/investmoz/unification/public-browser-validation.json @@ -1,87 +1,87 @@ { - "reports": [ - { - "path": "/", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Oportunidades reais de investimento em Moçambique, organizadas por sector." + "reports": [ + { + "path": "/", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Oportunidades reais de investimento em Moçambique, organizadas por sector." + }, + { + "path": "/oportunidades", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Catálogo de investimento" + }, + { + "path": "/sectores", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Sectores Prioritários" + }, + { + "path": "/como-investir", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Como Investir / Procedimentos" + }, + { + "path": "/legislacao", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Legislação e Incentivos" + }, + { + "path": "/documentos", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Documentos e Formulários" + }, + { + "path": "/apoio", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Apoio ao Investidor" + }, + { + "path": "/parcerias", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Bolsa de Parcerias" + }, + { + "path": "/simulador-fiscal", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Simulador Fiscal" + }, + { + "path": "/formularios/empresa", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Formulário de empresa" + }, + { + "path": "/login", + "breadcrumb": true, + "supportVisible": true, + "overflow": false, + "title": "Log in to your account" + } + ], + "simulation": "Sem redução: 100,00 MTnRedução simulada: 50,00 MTnTotal do cenário: 50,00 MTnBase × taxa ÷ 100 × (1 − redução ÷ 100). Não inclui outros impostos, deduções, escalões ou regras sectoriais.", + "desktop": { + "overflow": false, + "breadcrumb": true }, - { - "path": "/oportunidades", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Catálogo de investimento" - }, - { - "path": "/sectores", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Sectores Prioritários" - }, - { - "path": "/como-investir", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Como Investir / Procedimentos" - }, - { - "path": "/legislacao", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Legislação e Incentivos" - }, - { - "path": "/documentos", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Documentos e Formulários" - }, - { - "path": "/apoio", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Apoio ao Investidor" - }, - { - "path": "/parcerias", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Bolsa de Parcerias" - }, - { - "path": "/simulador-fiscal", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Simulador Fiscal" - }, - { - "path": "/formularios/empresa", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Formulário de empresa" - }, - { - "path": "/login", - "breadcrumb": true, - "supportVisible": true, - "overflow": false, - "title": "Log in to your account" - } - ], - "simulation": "Sem redução: 100,00 MTnRedução simulada: 50,00 MTnTotal do cenário: 50,00 MTnBase × taxa ÷ 100 × (1 − redução ÷ 100). Não inclui outros impostos, deduções, escalões ou regras sectoriais.", - "desktop": { - "overflow": false, - "breadcrumb": true - }, - "errors": [] -} \ No newline at end of file + "errors": [] +} diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 066dface..cbfce8c5 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -2,24 +2,24 @@ ## Matriz executável -| Menu / ecrã | Componente React | Campos / entidade / tabela | Rota ou API | Perfil / regra | Evidência | -| --- | --- | --- | --- | --- | --- | -| Início | `PortalInvestidor` | Sectores, oportunidades, recursos publicados | `/`, APIs existentes de homepage | Visitante; contagens do cadastro | `PublicPortalNavigationTest`, navegador | -| Catálogo / Educação | `investment/catalogue` | Filtros, educação, pivots, facetas | `/oportunidades`, `/educacao`; `/api/v1/investment/search` | Visitante; filtros em intersecção, montantes com moeda | `InvestmentUnificationTest` | -| Cluster / Corredor / Âncora | `portfolio/index`, `portfolio/show` | Dossiers, relações, cadeias e oportunidades | `/clusters`, `/corredores`, `/projectos-ancora`, `/carteira/{id}` | Visitante; dossier publicado; cadeia pública | `InvestmentPortfolioTest`, testes territoriais | -| Ficha | `investment/detail` | Oportunidade, contexto, relações, fonte | `/oportunidades/{slug}`; `/api/v1/investment/opportunities/{slug}` | Visitante; rascunhos não acessíveis | `PublicPortalNavigationTest`, `InvestmentUnificationTest` | -| Mapa | Componente `Map` em catálogo | `geo_locations`, `geo_geometries`, `opportunity_locations` | `/api/v1/investment/map` | Visitante; dois níveis de classificação pública | Teste PostGIS e estados vazios | -| Comparar / exportar / sugerir | `investment/catalogue` | IDs publicados, dados de catálogo | `/compare`, `/export`, `/suggest` sob prefixo da API | Visitante; 4 comparações, 1.000 linhas/pontos; CSV protegido | Teste de publicação e fórmula CSV | -| Área do investidor | `investment/workspace` | Manifestações, recibos, favoritos | `/investidor/manifestacoes`; `/api/v1/investment/submissions` | Titular autenticado e verificado | Isolamento por dono | -| Perfil / empresas | `investment/profile` | `investment_accounts`, `investment_company_declarations` | `/investidor/profile`; API `/profile`, `/companies/{id?}` | Titular; versão, encriptação, sem concessão de memberships | `InvestmentWorkspaceTest` e navegador | -| Conversa / versões | `investment/conversation` | `investment_messages`, `investment_submission_revisions` | API `/submissions/{id}/messages`, `/submissions/{id}/revisions/{version}` | Titular/gestor; mensagem idempotente; histórico privado | Testes de isolamento, encriptação e reenvio; navegador | -| Notificações | `investment/notifications` | `investment_notifications` | `/investidor/notifications`; API `/notifications`, `/{id}/read` | Destinatário; revogação de acesso a terceiros respeitada | Testes de mudança de estado e leitura; navegador | -| Manifestação | `investment/submission` | `investment_submissions.payload`, necessidades | `PUT /submissions/{id}`, `POST /submissions/{id}/submit` | Titular; versão, opções activas, consentimento, idempotência | Testes de conflito, validação e reenvio | -| Anexos | Etapa 4 da manifestação | `documents`, `investment_submission_attachments` | `/submissions/{id}/attachments` | Titular/gestor lê; titular altera rascunho; checksum | Teste de anexo privado e adulteração | -| Contexto da oportunidade | `investment/context` | Perfil, relações muitos-para-muitos, facetas | `/management/opportunities/{id}/context` | Gestor; versão e retorno a rascunho | Teste de gestão, conflito e educação | -| Referências e formulários | `investment/references` | Opções, rótulos, ordem, actividade | `/management/investment/references` | Gestor; códigos estáveis, Outro preservado | Teste de referências e reexecução do seed | -| Atendimento / CRM | `management/interest`, gestão CRM existente | `interest_requests`, `outbox_events`, leads | `/management/interests/{id}` | Gestor; consulta manifestação completa pelo vínculo | Testes de intake e CRM existentes | -| Apoio / documentos / parcerias | `portal/services`, `portal/manage` | Recursos, parcerias, pedidos existentes | `/apoio`, `/documentos`, `/parcerias`, `/balcao-virtual` | Regras existentes de publicação e dono | `PublicPortalServicesTest` | +| Menu / ecrã | Componente React | Campos / entidade / tabela | Rota ou API | Perfil / regra | Evidência | +| ------------------------------ | ------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | +| Início | `PortalInvestidor` | Sectores, oportunidades, recursos publicados | `/`, APIs existentes de homepage | Visitante; contagens do cadastro | `PublicPortalNavigationTest`, navegador | +| Catálogo / Educação | `investment/catalogue` | Filtros, educação, pivots, facetas | `/oportunidades`, `/educacao`; `/api/v1/investment/search` | Visitante; filtros em intersecção, montantes com moeda | `InvestmentUnificationTest` | +| Cluster / Corredor / Âncora | `portfolio/index`, `portfolio/show` | Dossiers, relações, cadeias e oportunidades | `/clusters`, `/corredores`, `/projectos-ancora`, `/carteira/{id}` | Visitante; dossier publicado; cadeia pública | `InvestmentPortfolioTest`, testes territoriais | +| Ficha | `investment/detail` | Oportunidade, contexto, relações, fonte | `/oportunidades/{slug}`; `/api/v1/investment/opportunities/{slug}` | Visitante; rascunhos não acessíveis | `PublicPortalNavigationTest`, `InvestmentUnificationTest` | +| Mapa | Componente `Map` em catálogo | `geo_locations`, `geo_geometries`, `opportunity_locations` | `/api/v1/investment/map` | Visitante; dois níveis de classificação pública | Teste PostGIS e estados vazios | +| Comparar / exportar / sugerir | `investment/catalogue` | IDs publicados, dados de catálogo | `/compare`, `/export`, `/suggest` sob prefixo da API | Visitante; 4 comparações, 1.000 linhas/pontos; CSV protegido | Teste de publicação e fórmula CSV | +| Área do investidor | `investment/workspace` | Manifestações, recibos, favoritos | `/investidor/manifestacoes`; `/api/v1/investment/submissions` | Titular autenticado e verificado | Isolamento por dono | +| Perfil / empresas | `investment/profile` | `investment_accounts`, `investment_company_declarations` | `/investidor/profile`; API `/profile`, `/companies/{id?}` | Titular; versão, encriptação, sem concessão de memberships | `InvestmentWorkspaceTest` e navegador | +| Conversa / versões | `investment/conversation` | `investment_messages`, `investment_submission_revisions` | API `/submissions/{id}/messages`, `/submissions/{id}/revisions/{version}` | Titular/gestor; mensagem idempotente; histórico privado | Testes de isolamento, encriptação e reenvio; navegador | +| Notificações | `investment/notifications` | `investment_notifications` | `/investidor/notifications`; API `/notifications`, `/{id}/read` | Destinatário; revogação de acesso a terceiros respeitada | Testes de mudança de estado e leitura; navegador | +| Manifestação | `investment/submission` | `investment_submissions.payload`, necessidades | `PUT /submissions/{id}`, `POST /submissions/{id}/submit` | Titular; versão, opções activas, consentimento, idempotência | Testes de conflito, validação e reenvio | +| Anexos | Etapa 4 da manifestação | `documents`, `investment_submission_attachments` | `/submissions/{id}/attachments` | Titular/gestor lê; titular altera rascunho; checksum | Teste de anexo privado e adulteração | +| Contexto da oportunidade | `investment/context` | Perfil, relações muitos-para-muitos, facetas | `/management/opportunities/{id}/context` | Gestor; versão e retorno a rascunho | Teste de gestão, conflito e educação | +| Referências e formulários | `investment/references` | Opções, rótulos, ordem, actividade | `/management/investment/references` | Gestor; códigos estáveis, Outro preservado | Teste de referências e reexecução do seed | +| Atendimento / CRM | `management/interest`, gestão CRM existente | `interest_requests`, `outbox_events`, leads | `/management/interests/{id}` | Gestor; consulta manifestação completa pelo vínculo | Testes de intake e CRM existentes | +| Apoio / documentos / parcerias | `portal/services`, `portal/manage` | Recursos, parcerias, pedidos existentes | `/apoio`, `/documentos`, `/parcerias`, `/balcao-virtual` | Regras existentes de publicação e dono | `PublicPortalServicesTest` | Os nomes curtos `/compare`, `/submissions`, etc. nesta matriz pertencem a `/api/v1/investment`. O contrato detalhado está em `openapi.json` (OpenAPI 3.0.3). @@ -63,11 +63,11 @@ Verificar teclado, ordem/foco, erros, contraste, zoom 200–400%, leitura assist Plano preparado, sessões ainda não realizadas: -| Sessão | Exercício verificável | Resultado esperado | -| --- | --- | --- | -| Investidor — 30 min | Filtrar, comparar, guardar necessidade, anexar e enviar em ambiente de formação | Um protocolo; nenhum dado visível por outro participante | -| Gestor — 45 min | Criar opção, associar contexto, rever publicação, consultar pedido e encaminhar | Fonte registada, rascunho antes de publicar, encaminhamento com responsável | -| Operação — 45 min | Restaurar backup isolado, verificar chave/anexos, simular falha de fila | Reconciliação documentada; nenhum envio duplicado | -| Acessibilidade — 30 min | Percurso por teclado e leitor de ecrã em móvel/computador | Registo de obstáculos e correcções antes de homologação | +| Sessão | Exercício verificável | Resultado esperado | +| ----------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| Investidor — 30 min | Filtrar, comparar, guardar necessidade, anexar e enviar em ambiente de formação | Um protocolo; nenhum dado visível por outro participante | +| Gestor — 45 min | Criar opção, associar contexto, rever publicação, consultar pedido e encaminhar | Fonte registada, rascunho antes de publicar, encaminhamento com responsável | +| Operação — 45 min | Restaurar backup isolado, verificar chave/anexos, simular falha de fila | Reconciliação documentada; nenhum envio duplicado | +| Acessibilidade — 30 min | Percurso por teclado e leitor de ecrã em móvel/computador | Registo de obstáculos e correcções antes de homologação | Backlog restante: editores territoriais completos e cartografia; ligação validada de empresas declaradas ao cadastro canónico; papéis especializados; retenção; homologação assistiva e carga; SuiteCRM no destino; sessões de formação e deploy monitorizado. A reconciliação remota foi adiada pelo proprietário para fase futura. Cada item exige implementação/evidência concreta, não nova aprovação genérica dos requisitos já autorizados. diff --git a/docs/investmoz/v2/ativacao-segunda-entrega.md b/docs/investmoz/v2/ativacao-segunda-entrega.md new file mode 100644 index 00000000..364fde01 --- /dev/null +++ b/docs/investmoz/v2/ativacao-segunda-entrega.md @@ -0,0 +1,51 @@ +# Preparação da ativação e segunda entrega + +8 de outubro de 2026. Código e evidências locais; a instalação na base ativa continua a exigir ação autenticada do utilizador. + +## Preparado para revisão + +A leitura ativa confirmou `investemoz:5432`, 76 migrations, Bolsa interna instalada e ausência do novo Balcão/acompanhamento. Continuam necessárias exatamente: + +1. `2026_10_07_190000_create_internal_virtual_desk`: cria `desk_service_versions`, `desk_cases`, `desk_requests`, `desk_documents`, `desk_events`, `desk_revisions`, `desk_messages`, `desk_notifications`, `desk_institutional_links` e `desk_payments`. +2. `2026_10_08_120000_extend_desk_investor_guidance`: acrescenta ordem dos eventos; associações opcionais a investidor/projeto/percurso/pedido existente; referências do Balcão, aceitação explícita e estado de sincronização nas tarefas existentes. Acrescenta índices, FKs e constraints; não copia contactos nem envia dados externos. + +O plano aparece em `/management/balcao-install`, acessível apenas a conta verificada com `manage-portal`. Mostra destino real, migrations pendentes, data do backup/restauro, compatibilidade, permissões DDL, QA desta versão, scan e reversão. O botão exige confirmação explícita e provas correspondentes ao código/destino. O backup expira para instalação após 24 horas; código alterado invalida QA/compatibilidade. O backend reapura o preview sob lock antes da transação e recusa prova de outra base ou preview alterado. + +No servidor local existente: **http://127.0.0.1:8000/management/balcao-install**. Depois de iniciar sessão com a sua conta autorizada, rever os indicadores e o plano, assinalar «Confirmo aplicar exactamente as alterações pré-visualizadas» e executar **Aplicar instalação autenticada**. Publicar um serviço de preparação interna é uma ação posterior e separada. Nenhuma ação de instalação foi executada nesta entrega. + +## Backup e restauro isolado + +`tools/desk-activation-backup.php` cria dump com snapshot PostgreSQL consistente de leitura, mantendo a conexão ativa `READ ONLY`. Restaurou exclusivamente em `desk_restore_20261008_093355_test:55441`; nome/porta reais foram verificados antes de escrever. Os conteúdos das 278 tabelas, incluindo schemas adicionais, coincidiram linha a linha/hash com o snapshot. + +O arquivo privado inclui os quatro ficheiros referenciados por `documents.storage_key` e `.env`, necessário para recuperar a configuração e a chave de cifragem. Cinco entradas foram lidas do arquivo, restauradas numa pasta privada isolada e comparadas por hash. O arquivo é confidencial, não publicado, não enviado a CRM ou outros serviços. O dump preserva a definição original; o restauro QA utiliza `--no-owner --no-privileges`, portanto não comprova a reprodução dos grants/roles de produção. + +Evidências privadas: `storage/app/private/virtual-desk/backup-proof.json`, dump/arquivo na pasta `activation-backup-*`, `activation-compatibility.json` e `activation-preview.json`. O dump verificado tem SHA-256 `78242c13b5a5942891a52aa86184cbba89f018e8ca9ec229d368d7e6743ddc1d`. Não há exportação de conteúdo pessoal no relatório. + +`tools/desk-activation-compatibility.php` ensaia as duas migrations apenas nessa cópia, verifica todos os campos/linhas originais das 278 tabelas e executa a reversão vazia em ordem inversa, comparando novamente os hashes. A reversão com dados novos não é destrutiva: os `down()` recusam eliminar dossiers, associações/tarefas e registos do Balcão. Depois de utilização real, preservar/exportar o novo histórico e reconciliar mudanças posteriores antes de qualquer restauro integral numa janela autorizada. Não aplicar dump antigo sobre alterações posteriores sem essa reconciliação. + +## ClamD e ficheiros reais + +Não foi encontrado serviço ClamD nem listener local na porta 3310. Foram novamente executados `ClamDScannerIntegrationTest` e `ClamDQueueWorkerIntegrationTest`: seis casos, três aprovados, três saltados, 13 asserções. Os três casos reais continuam sem evidência porque requerem daemon/assinaturas atuais. Não foi instalado ou simulado um serviço como substituto do scan real. + +O worker existente conserva ficheiros sem resultado conclusivo em quarentena/estado indeterminado; anexos recém-recebidos permanecem pendentes até scan. Download, uso documental seguro e conferência que requer ficheiro limpo continuam a exigir resultado válido para os bytes atuais. Guardar dossier/rascunho, alterar pressupostos e preparar pedidos internos permanece disponível. Não foi alterado qualquer estado de scan de documento ativo por esta preparação. + +## Segunda entrega em QA + +- **Mensagens:** no envio, um evento cifrado guarda ID e tipo da mensagem. Mudanças posteriores de permissões ou remoção de membro não reclassificam o histórico. Autoria, corpo e data permanecem. Registos antigos sem tipo documentado aparecem como «Mensagem histórica — tipo original não registado»; não há backfill por inferência de papel atual. +- **Coerência:** campos opcionais comparam procura/capacidade na mesma unidade/período, recursos necessários/disponíveis e financiamento necessário/comprometido na mesma moeda. Zero declarado é um valor; ausência nunca é zero. Sem unidade/período/moeda ou valores comparáveis, a conclusão é informação insuficiente. Lacunas e datas contraditórias são declarações contestáveis, não incompatibilidades oficiais ou bloqueio do percurso. Nenhuma previsão, taxa fiscal ou probabilidade de sucesso foi criada. +- **Ligações institucionais:** conferência interna explícita por gestor guarda evidência, autoria, URL, versão, contacto e data em auditoria. A validade de sete dias é uma política interna de disponibilidade, não prazo institucional/legal. Verificação ausente/expirada, URL inválida ou indisponibilidade produzem aviso e contacto alternativo/pedido no mesmo dossier. Serviços internos continuam disponíveis. Não há crawler, integração externa ou envio de dados; a disponibilidade real de cada URL exige consulta humana registada. O controlo de monitoria está preparado, mas links reais não foram inventados ou declarados verificados. +- **Permissões:** viewer/antigo membro não ganha acesso por existência do histórico; apenas leitores atualmente autorizados podem consultá-lo. Conferência de ligações é restrita à gestão, com conflito de versão rejeitado. + +QA global: **348 testes PHP, 345 aprovados, 3 saltados, 4065 asserções**; verificação específica final: **18 testes, 272 asserções**. Frontend: **151 aprovados**; TypeScript, lint, PHPStan e Pint passaram. Build concluído, mantendo os avisos já conhecidos de fontes opcionais e dimensão dos bundles. Uma primeira execução global coincidiu com a regeneração dos assets e falhou por CSS transitoriamente ausente; build e suite foram depois executados sequencialmente e a suite passou. + +O plano ativo reapurado apresenta **backup/restauro, QA, compatibilidade e permissões da base verificados**, com as duas migrations pendentes e ClamD indisponível. Evidências privadas: `second-phpunit.txt`, `second-targeted.txt`, `second-frontend.txt`, `second-types.txt`, `second-lint.txt`, `second-phpstan.txt`, `second-pint.txt`, `second-build.txt` e `activation-preview.json`. + +A revisão autenticada no navegador aprovou guardar os novos pressupostos, apresentar as comparações, registar a conferência de ligação, mostrar contacto alternativo e continuar o dossier internamente; aprovou também a apresentação do destino e da indisponibilidade ClamD na página de instalação QA. Zero erros JavaScript. Evidência: `second-browser-evidence.json` e captura privada `second-install-qa.png`. O teste de isolamento do plano final passou separadamente: um teste, 13 asserções. + +Os ficheiros alterados nesta fase constam de [ficheiros-segunda-entrega.csv](ficheiros-segunda-entrega.csv), com hashes para comparar antes de reverter. O CSV é um artefacto gerado e não inclui o seu próprio hash. + +O [backlog](backlog-mestre.md) distingue estes resultados da aplicação na base ativa. O snapshot `storage/app/private/investmoz-v2/second-before` preserva os ficheiros antes desta segunda entrega; reverter apenas os deltas correspondentes, sem reset global. Os três novos scripts `desk-activation-*` escrevem provas privadas ou na cópia QA; não instalam o Balcão na base ativa. + +## Aplicado na base ativa + +**Nenhuma alteração de esquema ou dados aplicada.** Houve leitura para diagnóstico e backup consistente. Instalação, publicação de serviços, scan real e utilização dos novos fluxos na base ativa não são resultados certificados por QA. A identidade do operador continua pendente e as integrações externas continuam sem ativação. diff --git a/docs/investmoz/v2/backlog-mestre.md b/docs/investmoz/v2/backlog-mestre.md new file mode 100644 index 00000000..42b899bd --- /dev/null +++ b/docs/investmoz/v2/backlog-mestre.md @@ -0,0 +1,73 @@ +# InvesteMoz V2 — backlog reconciliado da primeira entrega + +Data: 8 de outubro de 2026. Este backlog complementa [a auditoria existente](../requirements-audit.md) e [a matriz consolidada](../consolidated-review-matrix-2026-10-03.md); não substitui trabalho em curso. + +## Fonte e interpretação + +Documento indicado pelo utilizador: `melhorar e transformar o Portal do Investidor atual.docx`, pasta `Servicos Tempo/Relatorios`. Título interno: _Documento de Ecossistema Integrado de Apoio ao Investidor_, versão 2, 8 de outubro de 2026. SHA-256: `fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4`. + +O original mantém-se no acervo. Foram indexados todos os títulos reais, incluindo secções repetidas, em [indice-documento-mestre.csv](indice-documento-mestre.csv). Os números de parágrafo referem-se à ordem dos elementos `w:p` de `word/document.xml`, incluindo tabelas. A extração integral permanece privada. O índice não certifica a implementação nem a validade das afirmações da fonte. + +As instruções diretas do utilizador definem esta entrega: preservar ativos, consolidar Balcão e acompanhamento interno, não depender de interoperabilidade externa e testar exclusivamente em QA isolada. Exemplos SQL, seeds, pedidos formais, comandos e propostas organizacionais dentro do documento são referências de conceção. Não foram executados como instruções. Afirmações sobre taxas, prazos, diplomas, confirmação de áreas ou homologação precisam da respetiva evidência antes de qualquer regra automática. Os pesos e limiares propostos não constituem um modelo validado de sucesso. + +## Entrega atual e trabalho seguinte + +| ID | Referência na fonte | Situação nesta entrega | Próximo resultado verificável / dependência | +| ----- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| V2-01 | § inicial, p. 14–15; § 2.3, p. 679–680 | Menu comum por grupos e submenus, teclado/Escape/foco, toque e adaptação móvel implementados | Preservar navegação livre nas evoluções; a referência à proporção áurea é uma orientação visual, não uma certificação matemática de todas as páginas | +| V2-02 | § 3.3–3.4, p. 3340–3343 | Natureza privada e distinção de decisão institucional apresentadas; nome do operador configurável | Confirmar identidade jurídica, contactos, termos e política aplicáveis; não inventar operador ou acordo | +| V2-03 | § 3.2.2, p. 2172–2470; § 5.2, p. 7009–7190 | Mesmo dossier, pedido, conversa e tarefa interna; ciclo completo demonstrado em QA | Instalação ativa exige backup/restauro confirmado e ação autenticada existente; reconciliar requisitos de cada serviço sem bloquear preparação paralela | +| V2-04 | § 3.2.3.1, p. 2472–2532; § 5.2, p. 7009–7190 | Regras internas e recomendações explicáveis; evidência scoped com autoria/data/hash; aceitação distinta de tarefa | Expandir consistência entre mercado, capacidade, recursos e calendário com fontes verificáveis; IA externa continua opcional | +| V2-05 | § 3.2.3.2, p. 2533–2576; § 3.2.1.5, p. 1746–2160 | Calculadores existentes preservados; fronteira entre explicação e cálculo determinístico mantida | Reconciliar diplomas/artigos e parâmetros ausentes; nenhuma publicação documental ativa cálculos; não substituir desconhecidos por zero | +| V2-06 | § 3.2.3.3, p. 2577–2609; § 3.5, p. 3374–3453 | Bolsa instalada constatada por leitura ativa; acesso e contexto compartilhados | Avaliar restantes propostas de indicadores, contratos e estados contra implementação real; acordos CTA/câmaras/associações dependem de decisões humanas | +| V2-07 | § 3.2.3.4–3.2.3.10, p. 2610–3339 | Menu de gestão preserva rotas e permissões existentes | Dashboard integral, workflow editorial, métricas e APIs adicionais: reconciliação individual pendente; não criar tabelas paralelas a entidades existentes | +| V2-08 | § 4.6.1, p. 3740–6267 | Projetos/clusters/zonas pesquisáveis e fichas navegáveis em QA; esquema territorial ativo constatado | Revisão documental de cada área, limites/polígonos oficiais, disponibilidade, capacidade e histórico; links territoriais existentes não certificam todas as correções propostas | +| V2-09 | § 4.6.1.14–4.6.1.18, p. 4252–6267 | Nenhum score de sucesso, disponibilidade ou elegibilidade inventado | Validar pesos, lacunas, condicionantes e saídas explicáveis; não executar seeds demonstrativos contra dados ativos | +| V2-10 | § 4.1–4.7, p. 3646–6994 | Menus e preparação não limitados aos pilotos | Pilotos reais de agricultura, indústria e turismo; baseline e métricas com consentimento, responsáveis reais e cobertura inclusiva | +| V2-11 | § 5.1; § 5.2 didática–5.6, p. 7007–7008 e 7191–7558 | Inventário acessível atualizado: 248 ficheiros e 63 anexos incorporados; exemplares de formulários mantidos | Reconciliar todo o conteúdo do portal público, guias/árvores, autorização e atualização por ficheiro; inventário local não é crawl integral do site público | +| V2-12 | § 6, p. 7559–7687; § 3.2.1, p. 1453–2171 | Validação interna não equivale a licença, aprovação, benefício fiscal ou ato administrativo | Evidência jurídica/institucional de requisitos e formulários; integrações Banco de Moçambique/AT/APIEX e outras ficam fora desta fase | +| V2-13 | § 7–8, p. 7688–7881 | Snapshot local, guardas QA, testes e reversão de ficheiros documentados | Antes de release ativa: confirmar backup/restauro, compatibilidade e ação autenticada; não declarar implantação a partir de sucesso em QA | +| V2-14 | § 9–13, p. 7882–8008 | Limites da primeira entrega documentados | Nomeações, parcerias, transparência comercial, conflitos de interesse, SLAs e métricas exigem responsáveis e evidência reais | +| V2-15 | Repetições a partir de p. 8009 até 14242 | Todas as secções mantidas no índice, sem deduplicação destrutiva | Reconciliar diferenças entre versões repetidas antes de selecionar requisitos; nenhuma repetição implica requisito descartado ou concluído | + +## Pendências operacionais concretas + +- Instalação ativa do novo Balcão/acompanhamento: esquema ausente na leitura ativa; disponível e testado apenas no código/QA. Não alterar a base ativa por CLI de testes. +- Identidade concreta do operador: configuração preparada, confirmação documental pendente. +- CRM externo, pagamentos automáticos, SMS/WhatsApp e interoperabilidade institucional: fora desta fase. Referências/comprovativos e respostas internas não são confirmação por serviço externo. +- Formulários: inventário mantém exemplares, origem e campos; falta de confirmação não impede dossier/rascunho. Homologação não foi inferida. +- Scan real ClamD: três testes de integração saltados nesta máquina; fixtures QA usam um scanner simulado identificado. Nunca reutilizar esse resultado como evidência de segurança de ficheiros reais. +- Classificação histórica: corrigida na segunda entrega em QA. Novas mensagens conservam o tipo no evento original; antigas sem tipo registado ficam explicitamente indeterminadas, sem inferência por papel atual. +- Monitoria e contactos: controlo interno de conferência/expiração e alternativas concluído em QA; disponibilidade e contactos reais por serviço continuam a exigir consulta/evidência humana. Não foram ativadas consultas externas automáticas. + +O estado de cada secção no índice permanece «referência indexada; implementação individual não certificada». Apenas os resultados explicitamente demonstrados no [relatório da primeira entrega](primeira-entrega.md) são dados como verificados nesta fase. + +## Atualização — preparação da ativação e segunda entrega, 8 de outubro + +Ver [plano, restauro e segunda entrega](ativacao-segunda-entrega.md). Estados abaixo complementam a matriz inicial; sucesso em QA não é instalação ativa. + +| Requisito | Preparado / validado | Evidência | Aplicado na base ativa | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| V2-03, V2-13 — ativação | Duas migrations identificadas; plano autenticado reforçado com compatibilidade, permissões, prova da versão e backup válido por 24 h | `active-read-only.json`, `activation-preview.json`; página `/management/balcao-install` | Não; ação do utilizador autorizada continua necessária | +| V2-13 — backup/restauro | 278 tabelas comparadas, quatro documentos e configuração restaurados; migrations e reversão vazia ensaiadas na cópia | `backup-proof.json`, `activation-compatibility.json`; `desk_restore_20261008_093355_test:55441` | Apenas leitura/backup; nenhuma alteração de dados/esquema | +| V2-03 — mensagens | Tipo original preservado após mudança de permissões/remoção de membro; ausência histórica explícita, autoria conservada | `InvestorGuidanceTest`: tipo imutável e permissões de antigos membros | Não; novos fluxos ainda não instalados | +| V2-04 — coerência | Comparações declaradas com unidade/período/moeda; recursos/financiamento/calendário e lacunas não bloqueantes | `InvestorGuidanceRulesTest`; versão `investor-guidance-2026-10-08-v3` | Código local; sem gravações ativas de pressupostos | +| V2-12 — ligações | Conferência manual por responsável real, conflito de versão, expiração interna em sete dias e fallback de contacto/pedido | `InstitutionalLinkMonitor`, teste de auditoria/fallback sem chamadas externas | Sem novos links ou declarações de disponibilidade ativa | +| V2-11, V2-13 — scan | Indisponibilidade real confirmada; testes de falha/quarentena passaram; três casos reais saltados | `clamd-second.txt`: 6 casos, 3 passaram, 3 saltados, 13 asserções | Estados de scan ativos preservados; nenhum scan real declarado | +| V2-02, V2-06, V2-14 — operador/acordos/externos | Pendentes; identidade não inventada, integrações externas fora da fase | Limites no plano de ativação | Nenhuma ativação externa | + +As provas privadas ficam em `storage/app/private/virtual-desk` e `storage/app/private/investmoz-v2`; contêm apenas o necessário para revisão interna e restauro. Os ficheiros de backup e a chave/configuração não devem ser publicados. Os restantes itens e referências repetidas do documento mestre mantêm os estados anteriores. + +## Confirmação após ação autenticada do utilizador + +Leitura `READ ONLY` em 8 de outubro de 2026, 09:59 UTC: a base ativa `investemoz:5432` tem agora **78 migrations**, incluindo as duas do Balcão/acompanhamento. As tabelas e a extensão de tarefas/eventos estão presentes. A auditoria `desk_installed` regista a ação às **09:56:37 UTC**, pelo utilizador **13**, com o hash de código preparado `01e9d93d73f89a134a01d293c11ef92d69e63c18dbe2b4a12d9319d98d9e4a6b`. + +**V2-03/V2-13 — instalação: aplicada na base ativa pelo fluxo autenticado.** Há **dois serviços internos publicados**, zero dossiers e zero novos pedidos do Balcão nesta leitura. A publicação interna não homologa formulários nem constitui decisão institucional. Evidência privada: `active-confirmation.json` e `active-read-only.json`. Esta confirmação não escreveu dados ativos. + +ClamD, identidade do operador, evidência dos links reais e integrações externas mantêm as pendências anteriores; a instalação não equivale a validação operacional de um percurso real completo. + +### Primeiro percurso ativo + +O [registo do primeiro percurso](primeiro-percurso-ativo.md) identifica a pendência de sessão de navegador e dados reais. A leitura detalhada esclareceu o contador: os dois registos publicados são versões 1 e 2 de `project-preparation`, com o mesmo título, e não dois serviços distintos. Nenhuma versão foi alterada; nenhum dossier, pedido ou responsável foi criado para testar. + +**Correção do feedback do painel (V2-03):** criação de dossier confirma resultado/próxima ação; erros preservam campos; clique duplicado bloqueado durante o envio. QA: 19 testes PHP/287 asserções, navegador com persistência/recuperação e um único POST na fase de sucesso, sem submissão de pedido. Leitura ativa final às 10:13:50 UTC continua com zero dossiers/pedidos; nenhuma escrita real repetida. O motivo da tentativa anterior não é determinável apenas pela ausência de registo. diff --git a/docs/investmoz/v2/ficheiros-primeira-entrega.csv b/docs/investmoz/v2/ficheiros-primeira-entrega.csv new file mode 100644 index 00000000..b171259b --- /dev/null +++ b/docs/investmoz/v2/ficheiros-primeira-entrega.csv @@ -0,0 +1,92 @@ +ficheiro,existia_antes,alterações_anteriores,hash_antes,hash_depois +.github/workflows/tests.yml,True,False,e31fe5fdc093e5b265ccee70e0acfa0dcde9b3833add691ad0c5d03de4cef198,d5c6eed0833ca327044af87ffb4bd8900f410d5f5016765224f232d4de0af50f +app/Console/Commands/CheckMalwareScanner.php,True,False,2623cc6ab46b1919ab32cbabc0030639a20d90d1bf9ea0c2725a5afafe23b4a1,c2a3fe2b754b703a77cb506911ed028912bc0c7371fee26b78776a50687ab9e1 +app/Console/Commands/ImportIndustrialParkCandidates.php,True,False,edf1c76868324e26dc8622f0d27df33af13e4b53ecc2979fe607dba702bc08fe,5dd60087f41b4c4cfa9083ad0cb8b91e11a3be3a9868928abbb1cadaa0db762e +app/Console/Commands/ImportInvestmentZones.php,True,False,dfe005415ee57d6cc63afce828dfb0bfd42cd9f08076936ca8ba078a9d0a0409,5442bf352b489b3fdb0991c0307f0e47134c1a0978d0bb6ffe5fd6e94e75afe3 +app/Console/Commands/ImportLegalSourceArchive.php,True,True,76fa0c16c204d4ac9b840aa134866f8e76aeb06bb9cc82b5de9001e25ed43166,09cfec89c44805c96d1348223b124a7fb56137d17b8be7a729b17b424d97f9e3 +app/Http/Controllers/InvestmentZoneAttachmentController.php,True,False,e64d3846bd4580d15fb5c0688108723490563e700c613f9f0330c248ab420066,98a2866083ba9696561962bb1a7b9a27701000d2f2bf2723ca7fd47b319c525b +app/Http/Controllers/InvestmentZoneController.php,True,False,07aa6719ea91f2ac10968eaa9bd3bc3afcb7d90afcd11feaf4befd204dd02b94,b16b5efeeab13950b10de2a2a943ffe32a3345357838930212c09ae3f96d9bfa +app/Http/Controllers/PortalServicesController.php,True,True,f39c6782a1aa9e61799d06f8b50153a0924c9543962f5b9637f0260e5a119e10,08a1a9bcd44e0ba09087c66545a79b1c85703244f71cfe89e04a2ac559e521f7 +app/Http/Middleware/HandleInertiaRequests.php,True,True,ffd84e07de2ad63d2786b57dd04a4d853d7416d40b8b2e6b9f47e6ff56f7b2ae,0bc3bd7f6113726168ece809d60fa310877a66a5ca874a31d1b68cbda2e40d43 +app/Jobs/ScanDocumentForMalware.php,True,True,ea8280e700e02531c4ab56b37439cc3719cd5716efc991c5030c26a217d4737a,49fe9c6e60e1890ef600efd3426c5ee9fd40ca838cd4bfcbf9f698a684e029b2 +app/Models/CrmTask.php,True,True,757b0be029a13ab5700efef73a0ac235cb17484bbe828b5a011f941b01f3dd3a,01675a49d14685e27c391500f1d7e4c4c0d55af7ab4aa47b860bb2cae30fc4b5 +app/Models/InvestmentZone.php,True,False,7bd32ac4ba6facc78483050a0b37d297ff1d3315ab4ea4f8dae8b3ccdb9872a9,8489b2ecf71ba849c13e170efc878487b08dfc2de9d85d2b0d2ab82d00b99357 +app/Services/InvestmentZones.php,True,False,83410c3490d48a84071a7af14577eae699a127166840f9a5b24524cfc643187c,4ecacb6598b84673fa1f94118db3a54c065613e69c9516a43158904db6f102fd +app/Services/Malware/ArchiveLimitInspector.php,True,False,6f7283a87a52a336da3a9fd00843b2d07dc034016a44db0f8bc44c710f068f88,9b7efc0aedfd07806a926efe71fa7aa462d62f5922c871fa5799dcf8ae953693 +app/Services/Malware/ClamDScanner.php,True,False,27b140eafb0b4f013eb0355da1650205fce56caa2cc7b30123ee07da4f1f8f26,394833a9e2f3d82aa3ce0c29df6882f9bf3e6d6cb279ae4e13bea358f78ecf3f +app/Services/Malware/MalwareScanner.php,True,False,213c33316f3d464bab47a73ed47bce470a1b6f4efc51b0beffa9505b9e6c1fbf,162edca81f840dda27c515816e959b867a47f4e7f566f41d63c4540f56db3fc9 +bootstrap/app.php,True,True,9c1956352c6c43dd5c90858edf374470f23885fb4c8cae84bb2fbc1d4e062aba,7293722acfd9963b7b755fa4154f45ad804f57117afb8f697bb044c912cdb3b2 +config/portal.php,True,False,de8690ed2e1aa1be9a4439d645c8e975d97dac0b6886506bf61ed776ac833eab,40d2e4bcb21e99665a8be24d4e7bb7791d7774109d27730b30dbf4093288f1a7 +resources/js/app.tsx,True,True,caa193c988532816280adbc4493aad91bae51e2e317dc63f6908b17b20bd718d,5100723c21856e7778d9828a65a9eb27b2eae40041364b80416186a8518efd6f +resources/js/components/app-sidebar.tsx,True,False,640d184ef6dce65512ba6053069cf21ed62573d881d6905d861dbac5f3682450,aa8fb30cd55ec6a9a1ed6e70b3265bab414821d0d55093493c7bbb7e07da04e7 +resources/js/components/project-dossier-export.tsx,True,True,5a46b17ed8eb10652b384690bf4bb186d331465553e100da2c2c2f98306ba437,25d787f4d81354c4cd477603b5c0fc3e3b31d7644f75faa513a1f9a099408c47 +resources/js/components/simulation-form.tsx,True,True,0fdbf6065ba2a2b3e1dc2e45fe09b3f969e20a0b123b149aac6131d93292e609,1681b2e8b6887cb90e5db996fc74747fc6df774a89f57da18e18c058ed4cf053 +resources/js/layouts/app/app-sidebar-layout.tsx,True,True,1dd2a6b02c4d5c1b6a06b92661bc42c42bffeb1456cdd7112daa423f6f03d637,fc87bd85d4905b552a06255f850b1a5c1b8eefeb9b025e48e17770a4774781cd +resources/js/layouts/portal-navigation.tsx,True,True,a63d1ea80cd2a94ea8b149bc3b90ed62868e8944717689aab65515b935aa1fac,3a46d4359f3a96d6be652d1e7af8df28c1394764b52e3cb4fb26fd76de8b747e +resources/js/lib/business-financial-plan.ts,True,True,c3bb22de0e1c566b899e60b706c29fba83a9df9377d0c9575f218f7299bc14ab,b1239b12904508aa50ded1e903c014a54fccc2d950279918abbebe1481b2af2f +resources/js/lib/csv.js,True,False,5a5f1700b48ad646038a03df8aea0da0091e5d71103bf7b1b4f197518473255d,67deca744dc9cf35b4192d01f0dd79c819a09bd98ffc9a7f092f938f07d322ee +resources/js/lib/project-dossier-report.ts,True,True,6e51e73a821a22f4d239f42cc0ffd1f7ceac5c52232f3b1c3d20e1e47467304d,79f59571976c6cb3c56ac3287ed7b0be3da987deca9a6518230aeb5ed7e5e146 +resources/js/lib/project-dossier.ts,True,True,e14cfddf44749f7352c60abfd5cb989a84ed3f3048f68a8df0b1c236d0392b17,ce492036402fb504df71614e9ae83199c420fe25edcf0f89af10e2cfc873f28e +resources/js/pages/PortalInvestidor.tsx,True,False,e2fc4ed9e42ca467465b5fe73169fc1fadd1fd719c90d24d7c9265ec1d56db30,1b14090a94210f0d95dcbbe0bf6a4f0f99804feb6c10c69e4a4c9acdf4b376cd +resources/js/pages/ai/result.tsx,True,True,d6d79702f4a595b5a9dd0edfc50c0497aa90744a5ceaffc46fc5b5645b594ee5,6a860ed3bb95ca87a25268f581e55d6dd973d2453b62fd086567ebf94dd044e9 +resources/js/pages/crm/leads.tsx,True,True,d885e2996dda2b555176599ac3232360c17af5e7b691e43eebc23801913a8c5c,ae887cbe6642d532195119804f2a0e96900a941291474ce627d0b7d05710e134 +resources/js/pages/investment/submission.tsx,True,True,0520128b1950e5730a9a6a73a32dc2ede3c24a3f421fe75be165c713cb6e5749,b57cd9e5b81115eb08301ad58a2caed3fe5cd1208cce59c3ff55c4a271a922a1 +resources/js/pages/investment/zone.tsx,True,True,b7afdf27aee1ba3a2b05063d7bee795774f25e13cb1f16d1d8fe35073933b6a3,f1ee740396ab574e61a129b253162dec9a0826080c2edd85660f6d49bb94c0d7 +resources/js/pages/land-assistance/case.tsx,True,True,7b2bf923552fbf7ba196e7a2c177de92d5919e47659bc4053bb69948f736be9a,c740f2cdd90eefbad056903c2416f2f76ba7e6fc05a70fea74223484f199389c +resources/js/pages/land-assistance/print.tsx,True,True,58bdc51b037b4c44acd242a6e26f6c28d5a72739574cf935cdb2c59ed73a6605,1b46351abc92860dec63eca02ece870ca6dd13bb2034dfb92ebcd18eae6f6545 +resources/js/pages/portfolio/workspace.tsx,True,True,9e86103a318002131f40427d2c7f2d5b2036455f4de063e7ed53a6eb6852eead,b89fb9993b5b45f7ec77db07c9b8aaf5e359e3e7002e084fbbb424e3ad42eefa +tests/Feature/InterestSubmissionTest.php,True,False,eb71a300dedfafae68b741d64719261430a622d304c0d8380873f77a0fdb88de,10fabcc71d6fdfb0eab5110fceb20b14ac616ce7186090a609af3c171ebf4f4b +tests/Feature/InvestmentUnificationTest.php,True,False,381823afc5834edf3c44d417ced23a7d45b4544c53e8644ed0463e1a3f870c8f,ac96eca09a76d373c6ee0ce2ddaa1cef68fbb0611174e46631eecc313aa898f2 +tests/Feature/InvestmentZoneAttachmentTest.php,True,False,e710017c15eeb9044f8ead4d52e7b2312cbedee658253ac2af936d8085ad6350,a8c435588ed59f2b2be35ed6ddaee1ce09ad6782b8468bbda33ea78b6a7ac8ae +tests/TestCase.php,True,True,15dd6b69f191c4b6da3c0a5160f330df29316d063979df37efd8da7738f016ff,71e71e3b805c0bb6122bf065cfb03a534c3dcbbdad2772152240c03e2787777b +tests/frontend/business-financial-plan.test.mjs,True,False,8f8f60381bd40b127dec52d6abe8b2c148460e1b159d81fa803f5c2b825b509e,aafdcd6df991d2c90b02c900f80c232604ef79380a5dd879879a7ea082c5b36c +tests/frontend/business-locations.test.mjs,True,True,6a185aa854df526167fb25856352f4f436494349803562638f412bb7d1be14fb,1368ac6b42f82c0b020f13272ce5aafcfbc14eee38d82922d41317011379bb6e +tests/frontend/csv.test.mjs,True,True,828f92f98b2553ac82e6c650f92163dd15b56527876999abf6a0e104be0cf374,700be7053604dffc0cf6a5dfb56dd3841e3c4c2908162f41683400e92613d1db +tests/frontend/financial-calculators.test.mjs,True,False,c67f3b56eb6e2f6a03fd520d3c1142ebfccc03ce35ab8a152be0d230a0153a8b,62a52cf23cea1989bfc982246e033cf7e9e6f9e7d62175942c8a7d682a415727 +tests/frontend/financial-fiscal-bridge.test.mjs,True,False,d6ae344fe4ee293c22e812dc2a536735da123c730b636289ad66227140403494,7e1484d56c09cb75d2168c7a090f8fb7255816f04d91f667a7ba783ffba65412 +tests/frontend/monthly-cashflow.test.mjs,True,False,b23f444b52376b147c20aed0bbbc946376127a2d32728dd7d4628c388dbce995,e89086a439839242f5978758c7db76e4c8704fd9f9d07979ef110a89615e2e1a +tests/frontend/project-dossier-report.test.mjs,True,True,c74bfbe7104ca4aed5227e43d30e798e5d9363ad50732f25cc76bb31b7e1dc2b,b34b9dde8694255d851ae4b19990343a32bb8837b102067deb7b3ce851246d06 +tests/frontend/project-dossier.test.mjs,True,True,b1b8d08c8e7f2367de8e31bdc9a007aa4bc9cde254415ff7735c3e97de79fa31,247f60ed4f7ac900e892e5313835180aa10e29fbb312046009348d9d9356d343 +tests/frontend/project-readiness.test.mjs,True,True,4e492d707d50e3a9d26ff85103cb47fff4db5c04acb8500f1e4ec65cc6a49fe2,6af755e15aa79e6256107661874baba3dc04a6507809b61b6c034ebcf1a57624 +tests/frontend/simulation-api.test.mjs,True,False,9df2c18af6cdfe73c235b15399f461e534e076c05bd79e38813f714e6d1a8ef5,e26f0ccf59980c4a25890f1e074f433f73a6a8d26902b3d26a4a7eb3306d0d00 +tests/frontend/simulation-contract.test.mjs,True,False,f1214e70bc0932906065fc32005b65106a7b1c2d42e68bce1d059672e7a846dd,351f346881b926c49a88898f8f6575365492e5af63ee94c586f47f0e67939e28 +tests/frontend/simulation-draft-conversion-openapi.test.mjs,True,False,cc81bb80ae3d06835c4fe23729cd8a354042abd7e405fc965003902aa1cff41e,16ddff5ba37f98a31891e2c93f291f80fa7ae20aba7d5d2db1935651f4290f54 +tests/frontend/simulation-form.test.mjs,True,True,3113b69e0eb7f320c25ed5c864c7075a9a335cc971c137ee3aee5a5bc6cbb1df,ce6abe8030e0df780e8bb92e7713fb6f063c7ed6169edeadb5f2f30408cc6154 +tests/frontend/simulator-entry.test.mjs,True,False,21e5738eae7386805c96f448d809e5bf0290eb6645fb21764342c1b413cfe02e,b5dbdd0c79a0b1cbafd5ae167c4ee496810f510a5b23933583c8302aced9a99c +tests/frontend/territory-path.test.mjs,True,True,7cb8d26c7ed9e21953f2132c9906a3ac573d0a31dc7fd5d915baee5241e75994,ee3ad2faf5fba445ccb37ad6da9e5ef1bf18ddddb9f445ac4e971231d28287e1 +tools/business-wizard-browser.mjs,True,True,adb4153e824bfcf8da8d4631928290a07c6a799dbcb0420e6fe8d51ce22abdd7,e892233d0c59f819dbceefcafc47d34a41c22c8e0007122a76c154c9124b952e +tools/investment-map-browser.mjs,True,True,aee782431e96647a4ec91c96de33009643321aaad7369d68f392259cb8882f20,371285f8a6b64e54b27e25f05fa87bcfd4286c61ce2c87aa39d1b29295a138f0 +tools/investmoz-authorized-catalogue-review-browser.mjs,True,True,17282132bd5c44011841e90cb0bf83afbd5d2d288da3b3e7905fd6989b8e41a3,afda2edafdb0c5592a143e3f0a36b9ce0643b39e0d267fb8bd2aade9f5c96404 +tools/prepare-investment-relations.mjs,True,True,605427a95230b7ccbace4e960a6c238b4f29e413b67223ee3c80b6c8a33f3780,04bf72290211c1ecf77d76fd2eeb2d59ddb4a7162187ac08d8fc7b44f1ca61c0 +tools/territory-browser.mjs,True,True,e6bcd44491a38eedb7a051859db420e64c2258e9e8fc6a1377ba2474ef2927c3,59d43b26c09dd0dfdbfeac7953f75019cd5a8badfa8cb1cdcf38f5fe9f46a2d1 +tools/territory-opportunity-browser.mjs,True,True,68462b90ee3877e27d645b70b09556a4a0119be6b8540233db163d2c340082f2,84aa23652288e614daf5ededbf49b749d5c54407800da4613d82f524568eb204 +tools/territory-public-browser.mjs,True,True,e3831eebfaa75233d741814199d25d62c4210f072594719796823cb1e90cc192,60b9335ee3f2842c054156144e683ea034cf5869c4043d2c972dc02b8cb4dab0 +app/Http/Controllers/VirtualDeskOperationsController.php,True,True,b716fabdace3f1616d16fa329a09f806350082cf8390888386741feb8470d650,c7d8539e327cd83632e516f23b4b427806b86ca8ccdc9cad366a0f8ddb73b8c5 +app/Services/Ai/InvestorGuidance.php,True,True,d6a5fda393f5d92517c2337f6ee6e5dc72342bec7ba1ab3a2cf6de9d24030e77,ed041d6d91a590e1ad4e92b8624f6e0fa89dba75336c0329525335b87939086f +app/Services/Ai/InvestorGuidanceEvidence.php,False,False,,795e3aa445b0acb02ac992adc63fd5626ea5b4cc464d68fb69294b7db8433c14 +app/Services/Ai/InvestorGuidanceRules.php,True,True,d862acdb97613562bd5e4403b1f06c3eca3ceb195659988b0a79b235a5a34dde,039041a93c0e44d957ac70927e3101ed8c77484fe3aa6cf9103dc2f8550bb5de +docs/investmoz/v2/backlog-mestre.md,False,False,,bcd11823e1697f40d78ed202277ec1296ac185e20315ddd9b6e7e067b8f94487 +docs/investmoz/v2/indice-documento-mestre.csv,False,False,,4c13f1efd25a130c0d63e4a7f2efd9f9c2fbe9eb7ddee452534f06e8d7759bdd +docs/investmoz/v2/primeira-entrega.md,False,False,,91af08a26aff26afd420c3fd9164d391014ef224b19db5dbe0f00758a2ecc571 +resources/js/components/desk-internal-operations.tsx,True,True,5abf6389a8e0bf0ed249676a07c3de75e262546270892c834befaf354c94129e,f5b5050e6cd0390e16e0b6cfae114328702900786c0e90d4b8ac8b347e20996b +resources/js/components/investor-guidance.tsx,True,True,55b53c41cfe988e97e5cb238ce997e13ca083611f5107b606ae6a68739178bc9,9ed4de0dfbff0a0da713f77b71117d5742b7038d3b719282ea08e5086ead7f51 +resources/js/components/portal-grouped-menu.tsx,False,False,,4df819db001ca0925512be4a2e5b4d9fcda8d3ff65e926b4dfb1c3e047c064b4 +resources/js/lib/display-value.ts,False,False,,96d23210c16b2b87d343968173339b8942eda48ecf763ea4f2cc9212f2ec95be +resources/js/lib/portal-navigation.ts,False,False,,78064b94f7d71514f16930f8872e4fa3e609064ecccf314ce2f1e81586e1654f +resources/js/pages/desk/case.tsx,False,False,,664333f5d2ddad70ce3da2ab1e150569cb68016771ea136f5ad0b227a37aceac +resources/js/pages/desk/catalogue.tsx,False,False,,2ee2b9e4c19056839fdbef897b3abe1e05326778c0f93b78f75dd2655b937c1f +resources/js/pages/desk/install.tsx,False,False,,c78bc8ce3cf7c21708661c9910254b5ab4b442634438f56fe4f25a329a0c5455 +resources/js/pages/desk/panel.tsx,False,False,,06254ac92afc043a7cf95e8c9ec31daae7bad1f135f10d5c191e8a0356dd90d6 +resources/js/pages/desk/request.tsx,False,False,,67c52af574bbd66119ad5f762542f0edd148a9df21dcb5e2cc62790c6d33abdf +resources/js/pages/desk/sources.tsx,False,False,,09a65801fc2dc3209681b96c540473551aac213d938e8135a6b412a9d13d19c2 +tests/Feature/InvestorGuidanceTest.php,True,True,ac9659bd6e79c4bccba9814eecc11ce0783446e4dc6d939c9fff0b533f998156,9f13707342c627a78c5b9fa404f716d4527b1f88ae330b7dbb7b6b63ad2fd051 +tests/Support/CleanScannerFixture.php,False,False,,d187d4328fcde25ad3d2cfb4dd622633b3ccedc749adaba5ac50316490177110 +tests/Unit/InvestorGuidanceRulesTest.php,False,False,,a3ca3cd7f17c0f3ebf2eeca132659d702209b232c6f631da0e17698f4c482228 +tests/frontend/investor-guidance.test.mjs,True,True,09d563bebdec42be02e3bf8cfdf750d68f4fe3d047cf30742a24d0d21eea29c0,162a29e7fc0e10d4237739fea1e7ada55d0720487095d1347d120f65731da45b +tests/frontend/portal-navigation.test.mjs,False,False,,e0ac718944be994cca45adace15d580d17b83d4255f926bd5c7b3825d7c0f45a +tools/investmoz-v2-desk-browser.cjs,False,False,,387dc53f83b91fe299aca24a28c94319910ae684a905faae067d0cb61096dee7 +tools/investmoz-v2-navigation-browser.cjs,False,False,,405632e435e0811ec1b00d9f1794b05a54b6813668232041ad7ee99848415759 +tools/investmoz-v2-qa-fixtures.php,False,False,,56dfd6426127ad456aa3d0fd8db85fd76fe93ba04a5f50fee3f9ae7f0e068dbb +tools/investmoz-v2-qa-router.php,False,False,,171e8ad82d777aaac2c2957e4d74d7bff963a7dfd06f3c81847e653ffa5cc365 +tools/investor-guidance-browser.cjs,True,True,8b3b3e0d0dfc7566fec83b2ef44c668d28fbd4ce8bb84e9d88c8b3a90a7c5505,53346b97896310ac4ae30843e36ed7b37ed9f6d6bb6c7287f19dc4b19349da06 +tools/investor-guidance-qa-router.php,True,True,7b4071dcdb22e20c55812efcb7d3df17957a15c09bdaf4f82bb8c2483157e38f,c1760922db67eabb7ca36f662e86ef2f228ceb26da6df4c05df5f3619edef856 diff --git a/docs/investmoz/v2/ficheiros-segunda-entrega.csv b/docs/investmoz/v2/ficheiros-segunda-entrega.csv new file mode 100644 index 00000000..210591cb --- /dev/null +++ b/docs/investmoz/v2/ficheiros-segunda-entrega.csv @@ -0,0 +1,22 @@ +ficheiro,existia_antes,hash_antes,hash_depois +app/Http/Controllers/VirtualDeskController.php,True,a466272f471a564276269e7e0749d92cb84ab65316b7f56483fe18423aa8ed47,4ce1e877622999bc3a773819d7a42b274aa2ae7fad7111b9f5365bc3503a8dfd +tools/relationship-qa-seed.php,True,1d3170338342803f94abe1ef027af5a170a47e41670b513a02aa574990ade616,2e1f2bb8bd3caf0ff11c1487957ba95cd63164b8d11f6cca3db30136f7626453 +app/Http/Controllers/VirtualDeskInstallationController.php,True,c3d2ab7307ae6d8551b427a43b24c41b0dc05cc11c8892d86263e7782616d627,890c2608b0e747684ed027616dabe29a9628fd312abf29657f237bdf8f2caff7 +app/Services/Ai/InvestorGuidance.php,True,ed041d6d91a590e1ad4e92b8624f6e0fa89dba75336c0329525335b87939086f,8579a611736f22b16c3876918edaf79ffef14ba92d81fbb03200ea4260699c36 +app/Services/Ai/InvestorGuidanceRules.php,True,039041a93c0e44d957ac70927e3101ed8c77484fe3aa6cf9103dc2f8550bb5de,91f89953ff484420c2f78798a6048f231f4ad1adf8ef3907924621b787a6c78c +app/Services/InstitutionalLinkMonitor.php,False,,d72d6d0d0017bd0d085e59cd324e0986c3696d73a854489a4b6fa12cc81bf544 +app/Services/VirtualDeskInstallation.php,True,28119bbc212bb1f45146e722f475c131d9c96f9d17a94de48db02759fc29898a,ebd32e183edb54092ca64fb9b2ed3210536ce7fe632518cc0e18839d31715e0f +docs/investmoz/v2/ativacao-segunda-entrega.md,False,,c8acd38715c26b13b563afbc8ee02851c929af9448d2e78cb1656d16988803d7 +docs/investmoz/v2/backlog-mestre.md,True,bcd11823e1697f40d78ed202277ec1296ac185e20315ddd9b6e7e067b8f94487,e269e6844dcf8b53890c805ef56fca4783c00d7bf61009e4abb6df77f39463b5 +resources/js/components/investor-guidance.tsx,True,9ed4de0dfbff0a0da713f77b71117d5742b7038d3b719282ea08e5086ead7f51,66f12681140a1202f4b98e001039fcf7a9c142aeba874724e4147acfca2707af +resources/js/pages/desk/case.tsx,True,664333f5d2ddad70ce3da2ab1e150569cb68016771ea136f5ad0b227a37aceac,e6a833f7807dae109b2fb937ac03772dd6cd288e75c866947b1c3e230a065db5 +resources/js/pages/desk/install.tsx,True,c78bc8ce3cf7c21708661c9910254b5ab4b442634438f56fe4f25a329a0c5455,c0650f010595e0918e0bc8266b8b1ea81dfdc3364ddd2cbc433439e9fd80d8db +resources/js/pages/desk/request.tsx,True,67c52af574bbd66119ad5f762542f0edd148a9df21dcb5e2cc62790c6d33abdf,968d515b8a1c3c9e1bfc4b289859afd101567a58bbe7eab9e4bda536a632e7f2 +routes/virtual-desk.php,True,534ea4642683eeff737e2d37faa7ef33a786be78ad55c4aa845ddf1c7ba2230d,cff6864e0b261ea086b50261c65fd056d6ddcd8fa9d71d6ad1bb07a738136cd2 +tests/Feature/InvestorGuidanceTest.php,True,9f13707342c627a78c5b9fa404f716d4527b1f88ae330b7dbb7b6b63ad2fd051,0069887752b05d7a43f9a1aca277f5febd9f36fd40674dccc1b874b2bcbdd4f8 +tests/Unit/InvestorGuidanceRulesTest.php,True,a3ca3cd7f17c0f3ebf2eeca132659d702209b232c6f631da0e17698f4c482228,e3bea3ba3fe81416ca076416560a006c12c1aa0f9680372de3b1c7b46c0db00e +tools/desk-activation-backup.php,False,,c039da7a7babe52b268b39c7851c24e9005ec88ebd6b3d003d97574881552d58 +tools/desk-activation-compatibility.php,False,,1a622c49eb2ca5a53b67d5c4e38eacf3920607bb2f1a0b2b9c81502f6c9a8efb +tools/desk-activation-evidence.php,False,,dd6ab4cf95d9ce3d5ab3783c8f58cbb235cc090ebdab4d422258d2610cd3f109 +tools/investmoz-v2-qa-fixtures.php,True,56dfd6426127ad456aa3d0fd8db85fd76fe93ba04a5f50fee3f9ae7f0e068dbb,b93844f42165f1bf0d8bc72d7bdac6691743e707084894b6c4b31b3db7a4ce53 +tools/investmoz-v2-second-browser.cjs,False,,f3e7692519938b8418556bd81766382fcea42884ea8f86e081a408865f9faa9e diff --git a/docs/investmoz/v2/indice-documento-mestre.csv b/docs/investmoz/v2/indice-documento-mestre.csv new file mode 100644 index 00000000..ccca7680 --- /dev/null +++ b/docs/investmoz/v2/indice-documento-mestre.csv @@ -0,0 +1,615 @@ +fonte,sha256,paragrafo_inicio,paragrafo_fim,estilo,secção,estado +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8,658,Ttulo,Documento de Ecossistema Integrado de Apoio ao Investidor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,659,661,Ttulo1,1. Sumário executivo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,662,664,Ttulo2,1.1 Resultado esperado,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,665,665,Ttulo1,"2. Visão, objetivos e princípios",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,666,667,Ttulo2,2.1 Visão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,668,678,Ttulo2,2.2 Objetivos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,679,680,Ttulo2,2.3 Princípios orientadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,681,681,Ttulo1,3. Âmbito e atores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,682,683,Ttulo2,3.1 Âmbito funcional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,684,694,Ttulo2,3.2 Atores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,695,696,Ttulo2,3.2.1 Integração do Banco de Moçambique e da Autoridade Tributária de Moçambique (AT),Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,697,701,Ttulo3,3.2.1.1 Enquadramento normativo inicial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,702,743,Ttulo3,3.2.1.2 Fluxo transversal de licenciamento e controlo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,744,769,Ttulo3,3.2.1.3 Criação e licenciamento de instituições financeiras,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,770,856,Ttulo4,3.2.1.3.1 Matriz de licenciamento e supervisão do Banco de Moçambique,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,857,874,Ttulo4,3.2.1.3.2 Registo de controlo do licenciamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,875,882,Ttulo4,3.2.1.3.3 Critérios de prontidão e No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,883,978,Ttulo4,3.2.1.3.4 Ficha preenchível — Licenciamento e supervisão do Banco de Moçambique,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,979,985,Ttulo3,3.2.1.4 Operações cambiais e dívida externa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,986,1011,Ttulo3,3.2.1.5 Benefícios fiscais e isenções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1012,1022,Ttulo4,3.2.1.5.1 Fluxo detalhado de benefícios fiscais e isenções com a Autoridade Tributária de Moçambique (AT),Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1023,1416,Ttulo,Documento de Ecossistema Integrado de Apoio ao Investidor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1417,1419,Ttulo1,1. Sumário executivo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1420,1422,Ttulo2,1.1 Resultado esperado,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1423,1423,Ttulo1,"2. Visão, objetivos e princípios",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1424,1425,Ttulo2,2.1 Visão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1426,1436,Ttulo2,2.2 Objetivos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1437,1438,Ttulo2,2.3 Princípios orientadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1439,1439,Ttulo1,3. Âmbito e atores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1440,1441,Ttulo2,3.1 Âmbito funcional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1442,1452,Ttulo2,3.2 Atores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1453,1456,Ttulo2,3.2.1 Integração do Banco de Moçambique e da Autoridade Tributária de Moçambique (AT),Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1457,1461,Ttulo3,3.2.1.1 Enquadramento normativo inicial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1462,1503,Ttulo3,3.2.1.2 Fluxo transversal de licenciamento e controlo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1504,1529,Ttulo3,3.2.1.3 Criação e licenciamento de instituições financeiras,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1530,1616,Ttulo4,3.2.1.3.1 Matriz de licenciamento e supervisão do Banco de Moçambique,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1617,1634,Ttulo4,3.2.1.3.2 Registo de controlo do licenciamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1635,1642,Ttulo4,3.2.1.3.3 Critérios de prontidão e No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1643,1738,Ttulo4,3.2.1.3.4 Ficha preenchível — Licenciamento e supervisão do Banco de Moçambique,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1739,1745,Ttulo3,3.2.1.4 Operações cambiais e dívida externa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1746,1774,Ttulo3,3.2.1.5 Benefícios fiscais e isenções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1775,1852,Ttulo4,3.2.1.5.1 Fluxo detalhado de benefícios fiscais e isenções com a Autoridade Tributária de Moçambique (AT),Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1853,1874,Ttulo4,3.2.1.5.2 Catálogo mínimo de benefícios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1875,1908,Ttulo4,3.2.1.5.3 Matriz de evidências e reconciliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1909,1913,Ttulo4,"3.2.1.5.4 Estados, alertas e Go/No-Go",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1914,1915,Ttulo4,3.2.1.5.5 Indicadores do fluxo fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,1916,2016,Ttulo4,3.2.1.5.6 Ficha preenchível — Benefícios fiscais e isenções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2017,2160,Ttulo4,3.2.1.5.7 Lista de verificação — Validação jurídica e institucional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2161,2166,Ttulo3,"3.2.1.6 Integração com o simulador fiscal, CRM e repositório legal",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2167,2169,Ttulo3,3.2.1.7 Estados do processo e critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2170,2171,Ttulo3,3.2.1.8 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2172,2173,Ttulo2,3.2.2 Serviços empresariais integrados na jornada do investidor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2174,2182,Ttulo3,3.2.2.1 Fluxo integrado,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2183,2274,Ttulo3,3.2.2.2 Matriz de associação dos serviços,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2275,2308,Ttulo3,3.2.2.3 Ligações oficiais e modalidade de disponibilização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2309,2444,Ttulo3,"3.2.2.4 Matriz CRM de responsáveis, dependências e estados",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2445,2446,Ttulo4,3.2.2.4.1 Estados unificados do CRM,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2447,2453,Ttulo4,3.2.2.4.2 Regras de responsabilidade e escalonamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2454,2461,Ttulo3,3.2.2.5 Regras de integração,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2462,2470,Ttulo3,3.2.2.6 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2471,2471,Ttulo2,"3.2.3 Arquitectura inteligente, evolução de activos e administração do portal",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2472,2519,Ttulo3,3.2.3.1 Arquitectura de integração do CRM com a inteligência artificial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2520,2528,Ttulo4,3.2.3.1.1 Fluxo operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2529,2532,Ttulo4,3.2.3.1.2 Casos prioritários e limites,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2533,2571,Ttulo3,3.2.3.2 Plano de evolução do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2572,2576,Ttulo4,3.2.3.2.1 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2577,2606,Ttulo3,3.2.3.3 Critérios de preservação e melhoria da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2607,2609,Ttulo4,3.2.3.3.1 Estados e indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2610,2664,Ttulo3,3.2.3.4 Dashboard de administração e atualização integral,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2665,2673,Ttulo4,3.2.3.4.1 Fluxo de atualização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2674,2680,Ttulo4,3.2.3.4.2 Perfis e critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2681,2682,Ttulo3,3.2.3.5 Fluxos operacionais integrados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2683,2739,Ttulo4,3.2.3.5.1 Fluxo CRM–IA,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2740,2750,Ttulo4,3.2.3.5.2 Fluxo do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2751,2761,Ttulo4,3.2.3.5.3 Fluxo da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2762,2795,Ttulo4,3.2.3.5.4 Eventos de integração,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2796,2796,Ttulo3,3.2.3.6 Especificações técnicas do dashboard,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2797,2822,Ttulo4,3.2.3.6.1 Arquitetura lógica,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2823,2833,Ttulo4,3.2.3.6.2 Funcionalidades transversais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2834,2859,Ttulo4,3.2.3.6.3 APIs mínimas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2860,2879,Ttulo4,3.2.3.6.4 Requisitos não funcionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2880,2902,Ttulo4,3.2.3.6.5 Wireframes funcionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2903,2903,Ttulo3,3.2.3.7 Modelo de dados do dashboard,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2904,2953,Ttulo4,3.2.3.7.1 Entidades nucleares,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2954,2979,Ttulo4,3.2.3.7.2 Entidades por domínio,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2980,2987,Ttulo4,3.2.3.7.3 Regras de integridade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2988,2990,Ttulo4,3.2.3.7.4 Exemplo de esquema SQL,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,2991,3077,Ttulo3,3.2.3.8 Modelo de permissões,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3078,3084,Ttulo4,3.2.3.8.1 Regras obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3085,3146,Ttulo3,3.2.3.9 Workflow de publicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3147,3152,Ttulo4,3.2.3.9.1 Regras de transição,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3153,3158,Ttulo4,3.2.3.9.2 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3159,3339,Ttulo3,3.2.3.10 Matriz preenchível — Validação do modelo de dados e workflow,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3340,3341,Ttulo2,3.3 Limites da primeira versão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3342,3343,Ttulo2,3.4 Natureza privada e relacionamento institucional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3344,3366,Ttulo3,3.4.1 Modelo de relacionamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3367,3373,Ttulo3,3.4.2 Valor público gerado por iniciativa privada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3374,3375,Ttulo2,"3.5 Modelo de parceria com CTA, câmaras e associações",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3376,3409,Ttulo3,3.5.1 Modalidades de parceria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3410,3419,Ttulo3,3.5.2 Ciclo de adesão e gestão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3420,3429,Ttulo3,3.5.3 Cláusulas mínimas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3430,3451,Ttulo3,3.5.4 Governação das parcerias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3452,3453,Ttulo3,3.5.5 Indicadores de parceria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3454,3455,Ttulo2,3.6 Política de transparência e independência do portal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3456,3463,Ttulo3,3.6.1 Princípios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3464,3498,Ttulo3,3.6.2 Regras obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3499,3504,Ttulo3,3.6.3 Conflitos de interesse,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3505,3510,Ttulo3,"3.6.4 Publicidade, patrocínio e receitas",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3511,3512,Ttulo3,3.6.5 Transparência ativa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3513,3514,Ttulo3,3.6.6 Fiscalização e sanções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3515,3516,Ttulo3,3.6.7 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3517,3518,Ttulo2,3.7 Benefícios para investidores e entidades parceiras,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3519,3547,Ttulo3,3.7.1 Benefícios para investidores e promotores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3548,3554,Ttulo3,3.7.2 Benefícios para CTA,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3555,3561,Ttulo3,3.7.3 Benefícios para câmaras de comércio,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3562,3568,Ttulo3,3.7.4 Benefícios para associações empresariais e setoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3569,3574,Ttulo3,3.7.5 Benefícios para entidades públicas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3575,3580,Ttulo3,3.7.6 Benefícios para outros parceiros privados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3581,3610,Ttulo3,3.7.7 Matriz de valor e medição,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3611,3645,Ttulo1,4. Sectores prioritários,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3646,3647,Ttulo2,4.1 Sequência de pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3648,3668,Ttulo2,4.2 Critérios de seleção e desenho dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3669,3689,Ttulo2,4.3 Piloto 1 — Agricultura e agroindústria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3690,3710,Ttulo2,4.4 Piloto 2 — Indústria transformadora,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3711,3731,Ttulo2,4.5 Piloto 3 — Turismo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3732,3739,Ttulo2,4.6 Entregáveis comuns dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3740,3741,Ttulo3,4.6.1 Componente crítica de localização e inteligência territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3742,3748,Ttulo4,4.6.1.1 Objetivos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3749,3805,Ttulo4,4.6.1.2 Inventário geoespacial obrigatório,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3806,3826,Ttulo4,4.6.1.3 Princípio de cobertura territorial inclusiva,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3827,3851,Ttulo4,4.6.1.4 Arquitetura PostgreSQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3852,3853,Ttulo4,4.6.1.5 Modelo mínimo de registo territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3854,3862,Ttulo4,4.6.1.6 Fluxo de inventário e validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3863,3886,Ttulo4,4.6.1.7 Motor de avaliação de localização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3887,3888,Ttulo4,4.6.1.8 Monitoria do estado de evolução e uso dos recursos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3889,3904,Ttulo4,4.6.1.9 Governação e qualidade geoespacial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3905,3913,Ttulo4,4.6.1.10 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3914,3915,Ttulo4,4.6.1.11 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3916,3917,Ttulo4,4.6.1.12 Modelo lógico de dados geoespaciais em PostgreSQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3918,3924,Ttulo5,4.6.1.12.1 Princípios de modelação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,3925,4006,Ttulo5,4.6.1.12.2 Esquemas e entidades nucleares,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4007,4032,Ttulo5,4.6.1.12.3 Relações normalizadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4033,4041,Ttulo5,"4.6.1.12.4 Tipos, chaves e regras técnicas",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4042,4064,Ttulo5,4.6.1.12.5 Vistas e serviços de aplicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4065,4172,Ttulo4,4.6.1.13 Matriz de inventário das áreas e recursos territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4173,4251,Ttulo5,4.6.1.13.1 Grelha de cobertura do inventário,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4252,4253,Ttulo4,4.6.1.14 Método para identificar investimentos complementares por localização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4254,4264,Ttulo5,4.6.1.14.1 Etapas do método,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4265,4290,Ttulo5,4.6.1.14.2 Tipologia de complementaridade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4291,4333,Ttulo5,4.6.1.14.3 Grelha de pontuação das oportunidades,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4334,4341,Ttulo5,4.6.1.14.4 Fatores eliminatórios e condicionantes,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4342,4343,Ttulo5,4.6.1.14.5 Saída explicável,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4344,4349,Ttulo5,4.6.1.14.6 Validação e aprendizagem,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4350,4463,Ttulo4,4.6.1.14.7 Ficha de inventário dos parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4464,4502,Ttulo4,4.6.1.14.8 Classificação dos parques no modelo PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4503,4508,Ttulo5,4.6.1.14.8.1 Extensão SQL/PostGIS para parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4509,4614,Ttulo4,4.6.1.14.9 Exemplos de parques industriais e geolocalização das áreas identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4615,4619,Ttulo5,4.6.1.14.9.1 Seeds PostGIS para os registos identificados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4620,4646,Ttulo4,4.6.1.14.9 Exemplos de parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4647,4697,Ttulo4,4.6.1.14.10 Geolocalização das áreas já identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4698,4702,Ttulo4,4.6.1.14.11 Carga inicial no PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4703,4704,Ttulo4,4.6.1.14.12 Plano de obtenção e validação dos polígonos oficiais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4705,4735,Ttulo5,4.6.1.14.12.1 Regra de autenticação documental,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4736,4765,Ttulo5,4.6.1.14.12.2 Entidades responsáveis,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4766,4776,Ttulo5,4.6.1.14.12.3 Processo por lote,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4777,4805,Ttulo5,4.6.1.14.12.4 Testes técnicos obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4806,4828,Ttulo5,4.6.1.14.12.5 Estados e usos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4829,4890,Ttulo5,4.6.1.14.12.6 Matriz de trabalho das áreas identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4891,4898,Ttulo5,4.6.1.14.12.7 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,4899,5077,Ttulo4,4.6.1.14.13 Matriz final de acompanhamento dos polígonos oficiais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5078,5079,Ttulo5,4.6.1.14.13.1 Campos de controlo obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5080,5081,Ttulo4,4.6.1.14.14 Modelos de pedidos formais de dados geoespaciais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5082,5090,Ttulo5,"4.6.1.14.14.1 Pedido à APIEX — ZEE, ZFI e regimes especiais",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5091,5098,Ttulo5,4.6.1.14.14.2 Pedido à entidade de cadastro e cartografia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5099,5105,Ttulo5,4.6.1.14.14.3 Pedido à ADE e ao setor de transportes — corredores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5106,5113,Ttulo5,"4.6.1.14.14.4 Pedido ao ministério responsável pela indústria, BAU e gestores de parques",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5114,5120,Ttulo5,4.6.1.14.14.5 Pedido ao turismo/INATUR — zonas turísticas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5121,5127,Ttulo5,"4.6.1.14.14.6 Pedido à agricultura, regadios e gestores de áreas agroindustriais",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5128,5134,Ttulo5,"4.6.1.14.14.7 Pedido aos governos provinciais, municípios e distritos",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5135,5154,Ttulo5,4.6.1.14.14.8 Anexo técnico comum aos pedidos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5155,5156,Ttulo5,4.6.1.14.14.9 Prazo e seguimento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5157,5165,Ttulo4,4.6.1.15 Critérios de aceitação adicionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5166,5167,Ttulo4,4.6.1.15.0 Registo oficial inicial das áreas económicas e territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5168,5169,Ttulo5,4.6.1.15.0.1 Tratamento específico dos parques industriais e força probatória do Boletim da República,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5170,5199,Ttulo6,4.6.1.15.0.1.1 Modalidade dos parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5200,5236,Ttulo6,4.6.1.15.0.1.2 Suficiência do Boletim da República,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5237,5256,Ttulo6,4.6.1.15.0.1.3 Estados específicos dos parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5257,5263,Ttulo6,4.6.1.15.0.1.4 Uso dos parques no conceito,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5264,5265,Ttulo6,4.6.1.15.0.1.5 Dados adicionais para o inventário dos parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5266,5312,Ttulo5,4.6.1.15.0.1 Áreas com criação confirmada em fonte oficial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5313,5344,Ttulo5,"4.6.1.15.0.2 Áreas institucionais confirmadas, com instrumento ou delimitação ainda por reconciliar",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5345,5350,Ttulo5,4.6.1.15.0.3 Regra de substituição dos dados sintéticos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5351,5361,Ttulo5,4.6.1.15.0.4 Programa nacional de incorporação completa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5362,5382,Ttulo5,4.6.1.15.0.5 Critérios de completude e ativação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5383,5406,Ttulo4,4.6.1.15.1 Plano de execução e validação do SQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5407,5477,Ttulo4,4.6.1.15.2 Dados de exemplo para a matriz territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5478,5481,Ttulo5,4.6.1.15.2.1 Inserts demonstrativos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5482,5554,Ttulo4,4.6.1.15.3 Validação do algoritmo com cenários territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5555,5558,Ttulo5,4.6.1.15.3.1 Interpretação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5559,5581,Ttulo5,4.6.1.15.3.2 Testes de robustez,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5582,5583,Ttulo5,4.6.1.15.3.3 Resultado da validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5584,5598,Ttulo5,4.6.1.15.3.4 Evidências reais a recolher,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5599,5600,Ttulo4,4.6.1.16 Esquema SQL/PostGIS e tabelas principais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5601,5602,Ttulo5,4.6.1.16.1 Extensões e esquemas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5603,5604,Ttulo5,4.6.1.16.2 Tipos controlados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5605,5606,Ttulo5,4.6.1.16.3 Fontes e qualidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5607,5609,Ttulo5,4.6.1.16.4 Referencial territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5610,5614,Ttulo5,"4.6.1.16.5 Áreas económicas, corredores, clusters e lotes",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5615,5620,Ttulo5,"4.6.1.16.6 Ativos, recursos, riscos e mercados",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5621,5626,Ttulo5,"4.6.1.16.7 Oportunidades, indicadores e avaliações",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5627,5629,Ttulo5,4.6.1.16.8 Relações e monitoria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5630,5635,Ttulo5,4.6.1.16.9 Controlos adicionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5636,5637,Ttulo4,4.6.1.17 Modelo preenchível do inventário territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5638,5678,Ttulo5,4.6.1.17.1 Identificação e governação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5679,5716,Ttulo5,4.6.1.17.2 Localização e geometria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5717,5736,Ttulo5,"4.6.1.17.3 Estado, fundamento e temporalidade",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5737,5792,Ttulo5,"4.6.1.17.4 Área, ocupação e capacidade",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5793,5848,Ttulo5,4.6.1.17.5 Infraestruturas e acessibilidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5849,5892,Ttulo5,"4.6.1.17.6 Recursos, mercado e cadeia de valor",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5893,5930,Ttulo5,4.6.1.17.7 Condicionantes e riscos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5931,5962,Ttulo5,4.6.1.17.8 Oportunidade complementar,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5963,5989,Ttulo5,4.6.1.17.9 Evidências e aprovação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5990,5991,Ttulo4,4.6.1.18 Indicadores e algoritmo de pontuação das oportunidades,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,5992,6048,Ttulo5,4.6.1.18.1 Indicadores de entrada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6049,6105,Ttulo5,4.6.1.18.2 Pesos padrão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6106,6125,Ttulo5,4.6.1.18.3 Escala operacional 0–4,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6126,6133,Ttulo5,4.6.1.18.4 Fórmula,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6134,6150,Ttulo5,4.6.1.18.5 Regras de confiança,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6151,6184,Ttulo5,4.6.1.18.6 Fatores eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6185,6206,Ttulo5,4.6.1.18.7 Limiares de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6207,6219,Ttulo5,4.6.1.18.8 Algoritmo aplicável,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6220,6222,Ttulo5,4.6.1.18.9 Pseudocódigo SQL,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6223,6260,Ttulo5,4.6.1.18.10 Indicadores de desempenho do motor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6261,6267,Ttulo5,4.6.1.18.11 Governação do algoritmo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6268,6269,Ttulo2,4.7 Plano de continuidade e expansão sem restrição de âmbito,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6270,6278,Ttulo3,4.7.1 Garantias contra restrição pelos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6279,6307,Ttulo3,4.7.2 Serviços transversais obrigatórios desde a primeira onda,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6308,6337,Ttulo3,4.7.3 Fases do plano de continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6338,6339,Ttulo3,4.7.4 Continuidade para utilizadores fora dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6340,6341,Ttulo3,4.7.5 Critérios de expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6342,6357,Ttulo3,4.7.6 Critérios de Go/No-Go para continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6358,6359,Ttulo3,4.7.7 Indicadores de continuidade e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6360,6397,Ttulo3,4.7.8 Plano operacional de continuidade e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6398,6419,Ttulo3,4.7.9 Cadência de gestão da continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6420,6478,Ttulo3,4.7.10 Núcleo transversal comum a todos os setores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6479,6489,Ttulo3,4.7.11 Requisitos não funcionais do núcleo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6490,6512,Ttulo3,4.7.12 Níveis de cobertura setorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6513,6559,Ttulo3,4.7.13 Matriz de avaliação dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6560,6579,Ttulo3,4.7.14 Escala de pontuação e decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6580,6588,Ttulo3,4.7.15 Critérios eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6589,6598,Ttulo3,4.7.16 Processo de avaliação e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6599,6627,Ttulo3,4.7.17 Matriz de prioridade para novos setores e territórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6628,6629,Ttulo3,4.7.18 Plano de expansão por setor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6630,6631,Ttulo3,4.7.19 Grelha de avaliação aplicável aos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6632,6661,Ttulo4,4.7.19.1 Identificação da avaliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6662,6681,Ttulo4,4.7.19.2 Escala de classificação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6682,6800,Ttulo4,4.7.19.3 Grelha de pontuação ponderada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6801,6846,Ttulo4,4.7.19.4 Descritores e evidências mínimas por critério,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6847,6894,Ttulo4,4.7.19.5 Lista de verificação dos critérios eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6895,6943,Ttulo4,"4.7.19.6 Síntese, decisão e plano de ação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6944,6985,Ttulo4,4.7.19.7 Aprovação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6986,6994,Ttulo4,4.7.19.8 Regras de aplicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,6995,7006,Ttulo1,5. Arquitetura funcional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7007,7008,Ttulo2,5.1 Objeto mínimo do repositório legal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7009,7010,Ttulo2,"5.2 Preservação e evolução do CRM, inteligência artificial, simulador fiscal e Bolsa de Parceiros",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7011,7044,Ttulo3,5.2.1 CRM como espinha dorsal da jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7045,7079,Ttulo3,5.2.2 Inteligência artificial aplicada à jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7080,7088,Ttulo3,5.2.3 Preservação e melhoria do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7089,7097,Ttulo3,5.2.4 Preservação e melhoria da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7098,7123,Ttulo3,5.2.5 Inventário e decisão sobre as capacidades existentes,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7124,7125,Ttulo3,5.2.6 Critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7126,7126,Ttulo3,5.2.7 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7127,7128,Ttulo3,"5.1.1 Preservação e evolução do CRM, inteligência artificial, simulador fiscal e Bolsa de Parceiros",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7129,7148,Ttulo4,5.1.1.1 CRM como espinha dorsal da jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7149,7156,Ttulo4,5.1.1.2 Inteligência artificial aplicada à jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7157,7163,Ttulo4,5.1.1.3 Simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7164,7170,Ttulo4,5.1.1.4 Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7171,7187,Ttulo4,5.1.1.5 Inventário e decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7188,7190,Ttulo4,5.1.1.6 Go/No-Go e indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7191,7192,Ttulo2,5.2 Camada de informação didática e apoio à decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7193,7198,Ttulo3,5.2.1 Finalidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7199,7227,Ttulo3,5.2.2 Formatos didáticos obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7228,7229,Ttulo3,5.2.3 Metadados e controlos editoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7230,7238,Ttulo3,5.2.4 Processo de incorporação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7239,7245,Ttulo3,5.2.5 Princípios de decisão informada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7246,7247,Ttulo3,5.2.6 Indicadores de qualidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7248,7249,Ttulo2,5.3 Plano de incorporação e validação integral do conteúdo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7250,7256,Ttulo3,5.3.1 Âmbito do inventário,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7257,7318,Ttulo3,"5.3.2 Fases, entregáveis e portas de aprovação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7319,7320,Ttulo3,5.3.3 Registo mestre de conteúdo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7321,7322,Ttulo3,5.3.4 Amostragem e reconciliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7323,7328,Ttulo3,5.3.5 Tratamento de divergências,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7329,7330,Ttulo2,5.4 Estrutura dos guias didáticos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7331,7368,Ttulo3,5.4.1 Modelo obrigatório de guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7369,7375,Ttulo3,5.4.2 Níveis de guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7376,7383,Ttulo3,5.4.3 Regras editoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7384,7385,Ttulo3,5.4.4 Critérios de aprovação do guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7386,7387,Ttulo2,5.5 Estrutura das árvores de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7388,7422,Ttulo3,5.5.1 Componentes obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7423,7431,Ttulo3,5.5.2 Estrutura da saída,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7432,7440,Ttulo3,5.5.3 Ciclo de validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7441,7442,Ttulo3,5.5.4 Critérios de Go/No-Go das árvores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7443,7444,Ttulo2,"5.6 Critérios de atualização, arquivo e eliminação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7445,7478,Ttulo3,5.6.1 Matriz de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7479,7521,Ttulo3,5.6.2 Critérios de pontuação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7522,7530,Ttulo3,5.6.3 Regras de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7531,7556,Ttulo3,5.6.4 Periodicidade de revisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7557,7558,Ttulo3,5.6.5 Evidências obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7559,7687,Ttulo1,6. Matriz legal-operacional ampliada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7688,7697,Ttulo1,7. Transformação com continuidade operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7698,7699,Ttulo2,7.1 Arquitetura técnica indicativa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7700,7701,Ttulo1,8. Salvaguarda da Plataforma Existente,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7702,7711,Ttulo2,8.1 Princípios de salvaguarda,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7712,7737,Ttulo2,8.2 Ativos protegidos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7738,7789,Ttulo2,8.3 Plano de inventário técnico do portal Laravel,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7790,7832,Ttulo2,8.4 Critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7833,7839,Ttulo3,8.4.1 Gatilhos de rollback,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7840,7871,Ttulo2,"8.5 Política de backup, restauro e rollback",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7872,7881,Ttulo2,8.6 Sequência obrigatória para cada release,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7882,7912,Ttulo1,9. Governação e modelo operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7913,7914,Ttulo2,9.1 Fluxo de publicação legal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7915,7916,Ttulo2,9.2 Fluxo de aprovação de parcerias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7917,7918,Ttulo2,9.3 Registo público de parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7919,7944,Ttulo1,10. Roteiro de implementação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7945,7946,Ttulo2,10.1 Indicadores mínimos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7947,7967,Ttulo1,11. Riscos e controlos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7968,7992,Ttulo1,12. Critérios de aceitação da conceção,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,7993,8086,Ttulo1,13. Próximos passos imediatos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8087,8108,Ttulo4,3.2.1.5.2 Catálogo mínimo de benefícios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8109,8142,Ttulo4,3.2.1.5.3 Matriz de evidências e reconciliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8143,8147,Ttulo4,"3.2.1.5.4 Estados, alertas e Go/No-Go",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8148,8149,Ttulo4,3.2.1.5.5 Indicadores do fluxo fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8150,8250,Ttulo4,3.2.1.5.6 Ficha preenchível — Benefícios fiscais e isenções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8251,8393,Ttulo4,3.2.1.5.7 Lista de verificação — Validação jurídica e institucional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8394,8399,Ttulo3,"3.2.1.6 Integração com o simulador fiscal, CRM e repositório legal",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8400,8402,Ttulo3,3.2.1.7 Estados do processo e critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8403,8404,Ttulo3,3.2.1.8 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8405,8406,Ttulo2,3.2.2 Serviços empresariais integrados na jornada do investidor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8407,8415,Ttulo3,3.2.2.1 Fluxo integrado,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8416,8507,Ttulo3,3.2.2.2 Matriz de associação dos serviços,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8508,8541,Ttulo3,3.2.2.3 Ligações oficiais e modalidade de disponibilização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8542,8677,Ttulo3,"3.2.2.4 Matriz CRM de responsáveis, dependências e estados",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8678,8679,Ttulo4,3.2.2.4.1 Estados unificados do CRM,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8680,8686,Ttulo4,3.2.2.4.2 Regras de responsabilidade e escalonamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8687,8694,Ttulo3,3.2.2.5 Regras de integração,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8695,8703,Ttulo3,3.2.2.6 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8704,8704,Ttulo2,"3.2.3 Arquitetura inteligente, evolução de ativos e administração do portal",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8705,8752,Ttulo3,3.2.3.1 Arquitetura de integração do CRM com a inteligência artificial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8753,8761,Ttulo4,3.2.3.1.1 Fluxo operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8762,8765,Ttulo4,3.2.3.1.2 Casos prioritários e limites,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8766,8804,Ttulo3,3.2.3.2 Plano de evolução do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8805,8809,Ttulo4,3.2.3.2.1 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8810,8839,Ttulo3,3.2.3.3 Critérios de preservação e melhoria da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8840,8842,Ttulo4,3.2.3.3.1 Estados e indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8843,8897,Ttulo3,3.2.3.4 Dashboard de administração e atualização integral,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8898,8906,Ttulo4,3.2.3.4.1 Fluxo de atualização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8907,8913,Ttulo4,3.2.3.4.2 Perfis e critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8914,8915,Ttulo3,3.2.3.5 Fluxos operacionais integrados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8916,8972,Ttulo4,3.2.3.5.1 Fluxo CRM–IA,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8973,8983,Ttulo4,3.2.3.5.2 Fluxo do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8984,8994,Ttulo4,3.2.3.5.3 Fluxo da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,8995,9028,Ttulo4,3.2.3.5.4 Eventos de integração,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9029,9029,Ttulo3,3.2.3.6 Especificações técnicas do dashboard,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9030,9055,Ttulo4,3.2.3.6.1 Arquitetura lógica,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9056,9066,Ttulo4,3.2.3.6.2 Funcionalidades transversais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9067,9092,Ttulo4,3.2.3.6.3 APIs mínimas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9093,9112,Ttulo4,3.2.3.6.4 Requisitos não funcionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9113,9135,Ttulo4,3.2.3.6.5 Wireframes funcionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9136,9136,Ttulo3,3.2.3.7 Modelo de dados do dashboard,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9137,9186,Ttulo4,3.2.3.7.1 Entidades nucleares,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9187,9212,Ttulo4,3.2.3.7.2 Entidades por domínio,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9213,9220,Ttulo4,3.2.3.7.3 Regras de integridade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9221,9223,Ttulo4,3.2.3.7.4 Exemplo de esquema SQL,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9224,9310,Ttulo3,3.2.3.8 Modelo de permissões,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9311,9317,Ttulo4,3.2.3.8.1 Regras obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9318,9379,Ttulo3,3.2.3.9 Workflow de publicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9380,9385,Ttulo4,3.2.3.9.1 Regras de transição,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9386,9391,Ttulo4,3.2.3.9.2 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9392,9572,Ttulo3,3.2.3.10 Matriz preenchível — Validação do modelo de dados e workflow,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9573,9574,Ttulo2,3.3 Limites da primeira versão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9575,9576,Ttulo2,3.4 Natureza privada e relacionamento institucional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9577,9599,Ttulo3,3.4.1 Modelo de relacionamento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9600,9606,Ttulo3,3.4.2 Valor público gerado por iniciativa privada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9607,9608,Ttulo2,"3.5 Modelo de parceria com CTA, câmaras e associações",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9609,9642,Ttulo3,3.5.1 Modalidades de parceria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9643,9652,Ttulo3,3.5.2 Ciclo de adesão e gestão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9653,9662,Ttulo3,3.5.3 Cláusulas mínimas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9663,9684,Ttulo3,3.5.4 Governação das parcerias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9685,9686,Ttulo3,3.5.5 Indicadores de parceria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9687,9688,Ttulo2,3.6 Política de transparência e independência do portal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9689,9696,Ttulo3,3.6.1 Princípios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9697,9731,Ttulo3,3.6.2 Regras obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9732,9737,Ttulo3,3.6.3 Conflitos de interesse,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9738,9743,Ttulo3,"3.6.4 Publicidade, patrocínio e receitas",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9744,9745,Ttulo3,3.6.5 Transparência ativa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9746,9747,Ttulo3,3.6.6 Fiscalização e sanções,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9748,9749,Ttulo3,3.6.7 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9750,9751,Ttulo2,3.7 Benefícios para investidores e entidades parceiras,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9752,9780,Ttulo3,3.7.1 Benefícios para investidores e promotores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9781,9787,Ttulo3,3.7.2 Benefícios para CTA,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9788,9794,Ttulo3,3.7.3 Benefícios para câmaras de comércio,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9795,9801,Ttulo3,3.7.4 Benefícios para associações empresariais e setoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9802,9807,Ttulo3,3.7.5 Benefícios para entidades públicas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9808,9813,Ttulo3,3.7.6 Benefícios para outros parceiros privados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9814,9843,Ttulo3,3.7.7 Matriz de valor e medição,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9844,9878,Ttulo1,4. Setores prioritários,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9879,9880,Ttulo2,4.1 Sequência de pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9881,9901,Ttulo2,4.2 Critérios de seleção e desenho dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9902,9922,Ttulo2,4.3 Piloto 1 — Agricultura e agroindústria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9923,9943,Ttulo2,4.4 Piloto 2 — Indústria transformadora,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9944,9964,Ttulo2,4.5 Piloto 3 — Turismo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9965,9972,Ttulo2,4.6 Entregáveis comuns dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9973,9974,Ttulo3,4.6.1 Componente crítica de localização e inteligência territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9975,9981,Ttulo4,4.6.1.1 Objetivos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,9982,10038,Ttulo4,4.6.1.2 Inventário geoespacial obrigatório,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10039,10059,Ttulo4,4.6.1.3 Princípio de cobertura territorial inclusiva,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10060,10084,Ttulo4,4.6.1.4 Arquitetura PostgreSQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10085,10086,Ttulo4,4.6.1.5 Modelo mínimo de registo territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10087,10095,Ttulo4,4.6.1.6 Fluxo de inventário e validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10096,10119,Ttulo4,4.6.1.7 Motor de avaliação de localização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10120,10121,Ttulo4,4.6.1.8 Monitoria do estado de evolução e uso dos recursos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10122,10137,Ttulo4,4.6.1.9 Governação e qualidade geoespacial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10138,10146,Ttulo4,4.6.1.10 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10147,10148,Ttulo4,4.6.1.11 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10149,10150,Ttulo4,4.6.1.12 Modelo lógico de dados geoespaciais em PostgreSQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10151,10157,Ttulo5,4.6.1.12.1 Princípios de modelação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10158,10239,Ttulo5,4.6.1.12.2 Esquemas e entidades nucleares,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10240,10265,Ttulo5,4.6.1.12.3 Relações normalizadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10266,10274,Ttulo5,"4.6.1.12.4 Tipos, chaves e regras técnicas",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10275,10297,Ttulo5,4.6.1.12.5 Vistas e serviços de aplicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10298,10405,Ttulo4,4.6.1.13 Matriz de inventário das áreas e recursos territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10406,10484,Ttulo5,4.6.1.13.1 Grelha de cobertura do inventário,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10485,10486,Ttulo4,4.6.1.14 Método para identificar investimentos complementares por localização,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10487,10497,Ttulo5,4.6.1.14.1 Etapas do método,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10498,10523,Ttulo5,4.6.1.14.2 Tipologia de complementaridade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10524,10566,Ttulo5,4.6.1.14.3 Grelha de pontuação das oportunidades,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10567,10574,Ttulo5,4.6.1.14.4 Fatores eliminatórios e condicionantes,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10575,10576,Ttulo5,4.6.1.14.5 Saída explicável,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10577,10582,Ttulo5,4.6.1.14.6 Validação e aprendizagem,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10583,10696,Ttulo4,4.6.1.14.7 Ficha de inventário dos parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10697,10735,Ttulo4,4.6.1.14.8 Classificação dos parques no modelo PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10736,10741,Ttulo5,4.6.1.14.8.1 Extensão SQL/PostGIS para parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10742,10847,Ttulo4,4.6.1.14.9 Exemplos de parques industriais e geolocalização das áreas identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10848,10852,Ttulo5,4.6.1.14.9.1 Seeds PostGIS para os registos identificados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10853,10879,Ttulo4,4.6.1.14.9 Exemplos de parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10880,10930,Ttulo4,4.6.1.14.10 Geolocalização das áreas já identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10931,10935,Ttulo4,4.6.1.14.11 Carga inicial no PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10936,10937,Ttulo4,4.6.1.14.12 Plano de obtenção e validação dos polígonos oficiais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10938,10968,Ttulo5,4.6.1.14.12.1 Regra de autenticação documental,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10969,10998,Ttulo5,4.6.1.14.12.2 Entidades responsáveis,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,10999,11009,Ttulo5,4.6.1.14.12.3 Processo por lote,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11010,11038,Ttulo5,4.6.1.14.12.4 Testes técnicos obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11039,11061,Ttulo5,4.6.1.14.12.5 Estados e usos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11062,11123,Ttulo5,4.6.1.14.12.6 Matriz de trabalho das áreas identificadas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11124,11131,Ttulo5,4.6.1.14.12.7 Critérios de aceitação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11132,11310,Ttulo4,4.6.1.14.13 Matriz final de acompanhamento dos polígonos oficiais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11311,11312,Ttulo5,4.6.1.14.13.1 Campos de controlo obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11313,11314,Ttulo4,4.6.1.14.14 Modelos de pedidos formais de dados geoespaciais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11315,11323,Ttulo5,"4.6.1.14.14.1 Pedido à APIEX — ZEE, ZFI e regimes especiais",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11324,11331,Ttulo5,4.6.1.14.14.2 Pedido à entidade de cadastro e cartografia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11332,11338,Ttulo5,4.6.1.14.14.3 Pedido à ADE e ao setor de transportes — corredores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11339,11346,Ttulo5,"4.6.1.14.14.4 Pedido ao ministério responsável pela indústria, BAU e gestores de parques",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11347,11353,Ttulo5,4.6.1.14.14.5 Pedido ao turismo/INATUR — zonas turísticas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11354,11360,Ttulo5,"4.6.1.14.14.6 Pedido à agricultura, regadios e gestores de áreas agroindustriais",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11361,11367,Ttulo5,"4.6.1.14.14.7 Pedido aos governos provinciais, municípios e distritos",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11368,11387,Ttulo5,4.6.1.14.14.8 Anexo técnico comum aos pedidos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11388,11389,Ttulo5,4.6.1.14.14.9 Prazo e seguimento,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11390,11398,Ttulo4,4.6.1.15 Critérios de aceitação adicionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11399,11400,Ttulo4,4.6.1.15.0 Registo oficial inicial das áreas económicas e territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11401,11402,Ttulo5,4.6.1.15.0.1 Tratamento específico dos parques industriais e força probatória do Boletim da República,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11403,11432,Ttulo6,4.6.1.15.0.1.1 Modalidade dos parques industriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11433,11469,Ttulo6,4.6.1.15.0.1.2 Suficiência do Boletim da República,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11470,11489,Ttulo6,4.6.1.15.0.1.3 Estados específicos dos parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11490,11496,Ttulo6,4.6.1.15.0.1.4 Uso dos parques no conceito,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11497,11498,Ttulo6,4.6.1.15.0.1.5 Dados adicionais para o inventário dos parques,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11499,11545,Ttulo5,4.6.1.15.0.1 Áreas com criação confirmada em fonte oficial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11546,11577,Ttulo5,"4.6.1.15.0.2 Áreas institucionais confirmadas, com instrumento ou delimitação ainda por reconciliar",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11578,11583,Ttulo5,4.6.1.15.0.3 Regra de substituição dos dados sintéticos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11584,11594,Ttulo5,4.6.1.15.0.4 Programa nacional de incorporação completa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11595,11615,Ttulo5,4.6.1.15.0.5 Critérios de completude e ativação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11616,11639,Ttulo4,4.6.1.15.1 Plano de execução e validação do SQL/PostGIS,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11640,11710,Ttulo4,4.6.1.15.2 Dados de exemplo para a matriz territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11711,11714,Ttulo5,4.6.1.15.2.1 Inserts demonstrativos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11715,11787,Ttulo4,4.6.1.15.3 Validação do algoritmo com cenários territoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11788,11791,Ttulo5,4.6.1.15.3.1 Interpretação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11792,11814,Ttulo5,4.6.1.15.3.2 Testes de robustez,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11815,11816,Ttulo5,4.6.1.15.3.3 Resultado da validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11817,11831,Ttulo5,4.6.1.15.3.4 Evidências reais a recolher,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11832,11833,Ttulo4,4.6.1.16 Esquema SQL/PostGIS e tabelas principais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11834,11835,Ttulo5,4.6.1.16.1 Extensões e esquemas,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11836,11837,Ttulo5,4.6.1.16.2 Tipos controlados,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11838,11839,Ttulo5,4.6.1.16.3 Fontes e qualidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11840,11842,Ttulo5,4.6.1.16.4 Referencial territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11843,11847,Ttulo5,"4.6.1.16.5 Áreas económicas, corredores, clusters e lotes",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11848,11853,Ttulo5,"4.6.1.16.6 Ativos, recursos, riscos e mercados",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11854,11859,Ttulo5,"4.6.1.16.7 Oportunidades, indicadores e avaliações",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11860,11862,Ttulo5,4.6.1.16.8 Relações e monitoria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11863,11868,Ttulo5,4.6.1.16.9 Controlos adicionais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11869,11870,Ttulo4,4.6.1.17 Modelo preenchível do inventário territorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11871,11911,Ttulo5,4.6.1.17.1 Identificação e governação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11912,11949,Ttulo5,4.6.1.17.2 Localização e geometria,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11950,11969,Ttulo5,"4.6.1.17.3 Estado, fundamento e temporalidade",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,11970,12025,Ttulo5,"4.6.1.17.4 Área, ocupação e capacidade",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12026,12081,Ttulo5,4.6.1.17.5 Infraestruturas e acessibilidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12082,12125,Ttulo5,"4.6.1.17.6 Recursos, mercado e cadeia de valor",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12126,12163,Ttulo5,4.6.1.17.7 Condicionantes e riscos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12164,12195,Ttulo5,4.6.1.17.8 Oportunidade complementar,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12196,12222,Ttulo5,4.6.1.17.9 Evidências e aprovação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12223,12224,Ttulo4,4.6.1.18 Indicadores e algoritmo de pontuação das oportunidades,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12225,12281,Ttulo5,4.6.1.18.1 Indicadores de entrada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12282,12338,Ttulo5,4.6.1.18.2 Pesos padrão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12339,12358,Ttulo5,4.6.1.18.3 Escala operacional 0–4,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12359,12366,Ttulo5,4.6.1.18.4 Fórmula,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12367,12383,Ttulo5,4.6.1.18.5 Regras de confiança,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12384,12417,Ttulo5,4.6.1.18.6 Fatores eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12418,12439,Ttulo5,4.6.1.18.7 Limiares de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12440,12452,Ttulo5,4.6.1.18.8 Algoritmo aplicável,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12453,12455,Ttulo5,4.6.1.18.9 Pseudocódigo SQL,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12456,12493,Ttulo5,4.6.1.18.10 Indicadores de desempenho do motor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12494,12500,Ttulo5,4.6.1.18.11 Governação do algoritmo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12501,12502,Ttulo2,4.7 Plano de continuidade e expansão sem restrição de âmbito,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12503,12511,Ttulo3,4.7.1 Garantias contra restrição pelos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12512,12540,Ttulo3,4.7.2 Serviços transversais obrigatórios desde a primeira onda,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12541,12570,Ttulo3,4.7.3 Fases do plano de continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12571,12572,Ttulo3,4.7.4 Continuidade para utilizadores fora dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12573,12574,Ttulo3,4.7.5 Critérios de expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12575,12590,Ttulo3,4.7.6 Critérios de Go/No-Go para continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12591,12592,Ttulo3,4.7.7 Indicadores de continuidade e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12593,12630,Ttulo3,4.7.8 Plano operacional de continuidade e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12631,12652,Ttulo3,4.7.9 Cadência de gestão da continuidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12653,12711,Ttulo3,4.7.10 Núcleo transversal comum a todos os setores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12712,12722,Ttulo3,4.7.11 Requisitos não funcionais do núcleo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12723,12745,Ttulo3,4.7.12 Níveis de cobertura setorial,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12746,12792,Ttulo3,4.7.13 Matriz de avaliação dos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12793,12812,Ttulo3,4.7.14 Escala de pontuação e decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12813,12821,Ttulo3,4.7.15 Critérios eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12822,12831,Ttulo3,4.7.16 Processo de avaliação e expansão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12832,12860,Ttulo3,4.7.17 Matriz de prioridade para novos setores e territórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12861,12862,Ttulo3,4.7.18 Plano de expansão por setor,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12863,12864,Ttulo3,4.7.19 Grelha de avaliação aplicável aos pilotos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12865,12894,Ttulo4,4.7.19.1 Identificação da avaliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12895,12914,Ttulo4,4.7.19.2 Escala de classificação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,12915,13033,Ttulo4,4.7.19.3 Grelha de pontuação ponderada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13034,13079,Ttulo4,4.7.19.4 Descritores e evidências mínimas por critério,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13080,13127,Ttulo4,4.7.19.5 Lista de verificação dos critérios eliminatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13128,13176,Ttulo4,"4.7.19.6 Síntese, decisão e plano de ação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13177,13218,Ttulo4,4.7.19.7 Aprovação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13219,13227,Ttulo4,4.7.19.8 Regras de aplicação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13228,13239,Ttulo1,5. Arquitetura funcional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13240,13241,Ttulo2,5.1 Objeto mínimo do repositório legal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13242,13243,Ttulo2,"5.2 Preservação e evolução do CRM, inteligência artificial, simulador fiscal e Bolsa de Parceiros",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13244,13277,Ttulo3,5.2.1 CRM como espinha dorsal da jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13278,13312,Ttulo3,5.2.2 Inteligência artificial aplicada à jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13313,13321,Ttulo3,5.2.3 Preservação e melhoria do simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13322,13330,Ttulo3,5.2.4 Preservação e melhoria da Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13331,13356,Ttulo3,5.2.5 Inventário e decisão sobre as capacidades existentes,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13357,13358,Ttulo3,5.2.6 Critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13359,13359,Ttulo3,5.2.7 Indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13360,13361,Ttulo3,"5.1.1 Preservação e evolução do CRM, inteligência artificial, simulador fiscal e Bolsa de Parceiros",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13362,13381,Ttulo4,5.1.1.1 CRM como espinha dorsal da jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13382,13389,Ttulo4,5.1.1.2 Inteligência artificial aplicada à jornada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13390,13396,Ttulo4,5.1.1.3 Simulador fiscal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13397,13403,Ttulo4,5.1.1.4 Bolsa de Parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13404,13420,Ttulo4,5.1.1.5 Inventário e decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13421,13423,Ttulo4,5.1.1.6 Go/No-Go e indicadores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13424,13425,Ttulo2,5.2 Camada de informação didática e apoio à decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13426,13431,Ttulo3,5.2.1 Finalidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13432,13460,Ttulo3,5.2.2 Formatos didáticos obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13461,13462,Ttulo3,5.2.3 Metadados e controlos editoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13463,13471,Ttulo3,5.2.4 Processo de incorporação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13472,13478,Ttulo3,5.2.5 Princípios de decisão informada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13479,13480,Ttulo3,5.2.6 Indicadores de qualidade,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13481,13482,Ttulo2,5.3 Plano de incorporação e validação integral do conteúdo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13483,13489,Ttulo3,5.3.1 Âmbito do inventário,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13490,13551,Ttulo3,"5.3.2 Fases, entregáveis e portas de aprovação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13552,13553,Ttulo3,5.3.3 Registo mestre de conteúdo,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13554,13555,Ttulo3,5.3.4 Amostragem e reconciliação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13556,13561,Ttulo3,5.3.5 Tratamento de divergências,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13562,13563,Ttulo2,5.4 Estrutura dos guias didáticos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13564,13601,Ttulo3,5.4.1 Modelo obrigatório de guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13602,13608,Ttulo3,5.4.2 Níveis de guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13609,13616,Ttulo3,5.4.3 Regras editoriais,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13617,13618,Ttulo3,5.4.4 Critérios de aprovação do guia,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13619,13620,Ttulo2,5.5 Estrutura das árvores de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13621,13655,Ttulo3,5.5.1 Componentes obrigatórios,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13656,13664,Ttulo3,5.5.2 Estrutura da saída,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13665,13673,Ttulo3,5.5.3 Ciclo de validação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13674,13675,Ttulo3,5.5.4 Critérios de Go/No-Go das árvores,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13676,13677,Ttulo2,"5.6 Critérios de atualização, arquivo e eliminação",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13678,13711,Ttulo3,5.6.1 Matriz de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13712,13754,Ttulo3,5.6.2 Critérios de pontuação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13755,13763,Ttulo3,5.6.3 Regras de decisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13764,13789,Ttulo3,5.6.4 Periodicidade de revisão,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13790,13791,Ttulo3,5.6.5 Evidências obrigatórias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13792,13920,Ttulo1,6. Matriz legal-operacional ampliada,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13921,13930,Ttulo1,7. Transformação com continuidade operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13931,13932,Ttulo2,7.1 Arquitetura técnica indicativa,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13933,13934,Ttulo1,8. Salvaguarda da Plataforma Existente,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13935,13944,Ttulo2,8.1 Princípios de salvaguarda,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13945,13970,Ttulo2,8.2 Ativos protegidos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,13971,14022,Ttulo2,8.3 Plano de inventário técnico do portal Laravel,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14023,14065,Ttulo2,8.4 Critérios de Go/No-Go,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14066,14072,Ttulo3,8.4.1 Gatilhos de rollback,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14073,14104,Ttulo2,"8.5 Política de backup, restauro e rollback",Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14105,14114,Ttulo2,8.6 Sequência obrigatória para cada release,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14115,14145,Ttulo1,9. Governação e modelo operacional,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14146,14147,Ttulo2,9.1 Fluxo de publicação legal,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14148,14149,Ttulo2,9.2 Fluxo de aprovação de parcerias,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14150,14151,Ttulo2,9.3 Registo público de parceiros,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14152,14177,Ttulo1,10. Roteiro de implementação,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14178,14179,Ttulo2,10.1 Indicadores mínimos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14180,14200,Ttulo1,11. Riscos e controlos,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14201,14225,Ttulo1,12. Critérios de aceitação da conceção,Referência indexada; implementação individual não certificada +melhorar e transformar o Portal do Investidor atual.docx,fc77136358e1a66561dcc5e80ff1cf07171e66f7662744ba382b932fa3be9ad4,14226,14242,Ttulo1,13. Próximos passos imediatos,Referência indexada; implementação individual não certificada diff --git a/docs/investmoz/v2/primeira-entrega.md b/docs/investmoz/v2/primeira-entrega.md new file mode 100644 index 00000000..33d01ac1 --- /dev/null +++ b/docs/investmoz/v2/primeira-entrega.md @@ -0,0 +1,75 @@ +# InvesteMoz V2 — primeira entrega local + +8 de outubro de 2026. Entrega de código local; sem implantação ou escrita na base ativa. + +## Estado inicial e preservação + +O trabalho começou sobre `bdd766c68a5e09437f5bf37f47021d49bd43211a`, com 309 entradas locais já modificadas/não rastreadas. O Balcão e o acompanhamento tinham alterações em curso. Foram preservados esses ativos, incluindo ficheiros ZIP e documentação anteriores; não houve reset, limpeza, commit ou substituição global. + +Foi guardado um snapshot privado em `storage/app/private/investmoz-v2/before/inventory.json`, com bytes originais, hashes e HEAD. Os ficheiros originalmente limpos afetados por correções de CI também foram preservados. [ficheiros-primeira-entrega.csv](ficheiros-primeira-entrega.csv) identifica o delta desta entrega, distinguindo ficheiros com alterações anteriores. Não representa todo o `git diff` acumulado. + +A leitura da base ativa usou PDO em transação `READ ONLY`, sem arrancar providers, com `ROLLBACK` final. Evidência privada: `active-read-only.json`. Identidade real: `investemoz`, porta 5432; 76 migrations instaladas. A Bolsa de Parcerias interna está instalada. O novo esquema `desk_cases` e a extensão de tarefas/eventos do acompanhamento não estavam instalados. Existem associações territoriais: 9 primárias, 15 secundárias e 4 de impacto; o audit mostra 6 gravações de território primário, 1 aceite do proprietário e 3 ativações de nomes de referência. Estes agregados não certificam todas as reconciliações territoriais por projeto. + +## Implementação e reutilização + +O catálogo partilhado de navegação organiza Explorar, Preparar, Serviços, Parcerias, Minha área e Gestão autorizada. Mantém rotas existentes, submenus, indicação de grupo/página atuais, preparação em paralelo e acesso direto aos módulos. O layout público e o interno reutilizam o mesmo catálogo. O menu de gestão é condicionado pelas permissões; endpoints mantêm a autorização independente do menu. + +Os menus permitem rato, toque e teclado; Escape fecha o submenu e devolve o foco. Os painéis respeitam os limites da janela e permitem scroll. Foram corrigidas sobreposição do cabeçalho, quebra móvel e sobreposição do contacto flutuante. O campo de pesquisa da página inicial ganhou fundo/contraste legível. A proporção áurea referida na fonte orienta a composição; não foi aplicada uma fórmula rígida a todas as páginas. + +O portal apresenta a natureza privada/independente e reserva decisões oficiais às instituições competentes. `PORTAL_OPERATOR_NAME` permite reutilizar uma identidade confirmada. Não foi encontrada confirmação suficiente para preencher esse nome; não foi inventado um operador. + +O acompanhamento reutiliza dossier, pedidos, documentos, eventos e tarefas existentes. Não cria um segundo balcão. Recomendações automáticas, submissões do investidor, conferência/parecer interno e resposta humana permanecem distintos nos fluxos demonstrados. Uma sugestão só origina tarefa após ação explícita do investidor; a tarefa aponta para o mesmo pedido no CRM interno. O contexto acompanha roteiro, simulador, Bolsa e outras páginas, com recuperação de rascunhos. + +A avaliação de evidência distingue informação ausente, declaração, anexo por conferir, verificação interna, problema/contradição, expiração, substituição e indisponibilidade técnica. Uma conferência só sustenta os âmbitos selecionados pelo verificador real. Guarda autoria, data, o que foi verificado, evidência, versão e hash; uma alteração de bytes/versão ou uma conferência posterior pode invalidar o apoio anterior. A visibilidade respeita proprietário, gestor e analista atribuído ao pedido. O fingerprint do contexto inclui a evidência, evitando reutilizar uma recomendação aceite quando os seus pressupostos mudam. + +Os alertas de mercado, localização, recursos e financiamento dependem da lacuna efetiva. Um campo preenchido ou um anexo não valida procura, infraestrutura, financiamento ou elegibilidade. Verificação interna não elimina limitações nem se transforma em aprovação oficial. Os cálculos continuam nas ferramentas determinísticas existentes; a IA não fornece taxas ou valores substitutos. As regras internas funcionam sem modelo externo. + +## Código, QA e instalação ativa + +| Capacidade | Código local/QA | Base ativa constatada | +| -------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------- | +| Navegação partilhada e aviso de natureza privada | Implementado, build local e navegador | Não é prova de publicação/implantação | +| Bolsa, CRM interno, dossier e calculadores preexistentes | Reutilizados | Bolsa/esquema interno existente instalado | +| Novo Balcão, conferência e acompanhamento | Implementado e testado no esquema completo QA | Novas tabelas/extensão ausentes; instalação autenticada pendente | +| Associações territoriais | Rotas/detalhes/pesquisa testados | Esquema e relações existentes constatados; completude documental por área não certificada | +| IA/CRM externos e comunicações/pagamentos automáticos | Não necessários ao ciclo interno | Integrações não ativadas nem verificadas nesta entrega | + +Não foi executada migration, seed, teste ou importação contra a base ativa. A ação autenticada de instalação já prevista no projeto continua necessária, depois de confirmar backup/restauro aplicável. O snapshot de ficheiros local não substitui esse backup da base. + +## Evidência de testes + +Todos os testes de base de dados usaram `investmoz_v2_20261008_test`, loopback, porta 55441. As guardas rejeitam o nome da base ativa, outra porta e URLs de conexão alternativas. O servidor QA utiliza sessões separadas, contas sintéticas, autenticação normal e integrações externas desativadas. Os testes de navegador bloqueiam pedidos externos; falha de tiles do mapa não impediu os serviços internos. + +| Verificação | Resultado | +| ------------------------------------------------ | ---------------------------------------------------------------------------- | +| PHPUnit completo | 344 testes: 341 passaram, 3 saltados; 4009 asserções | +| Ciclo PostGIS/migrations, separado | 1 teste passou, 3106 asserções | +| Frontend `node --test tests/frontend/*.test.mjs` | 151 passaram, 0 falhas | +| TypeScript | Passou | +| Lint/frontend | Sem erros ou avisos, 231 ficheiros | +| PHPStan e Pint | Passaram, sem baseline/ignores novos para esconder erros | +| Build | Concluído; avisos existentes de `fontaine` opcional e chunks acima de 500 kB | + +Os três testes saltados requerem integração ClamD real. A QA documental usa um scanner simulado explicitamente identificado; não constitui scan real ou homologação. + +O ciclo do Balcão foi demonstrado no navegador: guardar/reabrir rascunho → submeter explicitamente → atribuir responsável → pedir esclarecimento → corrigir/re-submeter → parecer/conclusão interna → resposta humana → aceitar recomendação → tarefa no CRM interno → recuperar contexto na Bolsa. Evidência privada: `desk-browser-evidence.json`; dossier `414615fc-5fb5-4c1a-8cd8-5cde74c3e68a`, pedido `87a162f4-f445-4651-95aa-9ecce30a0e2f`, tarefa `ec86398e-ae0c-46d3-9e93-b3aaede71d54`. São exclusivamente fixtures QA. + +A validação adicional de navegação passou em desktop 1440 px e móvel 390 px, áreas pública/investidor/gestão, submenus e foco, pesquisa/fichas de projeto, cluster e parque, recuperação de um rascunho anterior, contexto entre módulos, conferência pela interface com autoria e isolamento entre utilizadores. Zero erros JavaScript de página. Evidência: `navigation-browser-evidence.json`; capturas verificadas visualmente permanecem em `storage/app/private/investmoz-v2`. + +## Defeitos anteriores e regressões encontradas + +O diagnóstico inicial encontrou 97 erros PHPStan e avisos de lint que impediam o CI global. Foram corrigidos tipos/contratos reais, leitura segura de ficheiros, valores escalares, tratamento de resultados indisponíveis e promessas dos testes frontend, sem desativar verificações. Quatro falhas de testes anteriores envolviam expectativa HTTP 500 já substituída pelo tratamento 503, redirecionamento do Balcão instalado e fixtures sem scanner determinístico. Mantiveram-se as asserções de rollback, idempotência e segurança. + +Durante esta entrega, o navegador encontrou sobreposição do menu e ambiguidade no rótulo do seletor documental; ambas foram corrigidas. Um autofix de lint alterou o tipo da comparação ZIP; foi corrigido mantendo a comparação dos bytes e repetindo os 151 testes. Os avisos de dimensão dos bundles/fontes e os testes ClamD saltados permanecem identificados. + +## Acervo, dependências e pendências + +O inventário integral dos dois diretórios de fontes acessíveis foi atualizado antes da seleção: 248 ficheiros e 63 anexos incorporados, incluindo o documento indicado posteriormente. Os originais, hashes, proveniência e o inventário anterior mantêm-se privados. Consulta, situação documental e uso em regra/cálculo são estados independentes; nenhum lote foi publicado. Os exemplares de formulários continuam no registo privado existente; não houve confirmação nova de homologação, taxas, prazos ou referências legais. + +O [backlog mestre](backlog-mestre.md) relaciona a primeira entrega e os requisitos restantes com parágrafos/hash da fonte. Falta confirmar o operador, instalar o novo esquema na base ativa pelo fluxo autenticado após backup/restauro, reconciliar integralmente conteúdo/formulários e evidência jurídica/territorial, executar ClamD real e auditar a classificação histórica de mensagens de antigos membros. CRM externo, interoperabilidade, SMS/WhatsApp e confirmação automática de pagamentos ficam fora desta fase. Referências e comprovativos internos não são prova de confirmação por um serviço externo. + +## Ficheiros e reversão + +As áreas principais são `.github/workflows/tests.yml`, catálogo/componente/layouts da navegação, serviço/regras de evidência, interface de conferência/acompanhamento e testes/routers QA. A lista exata e hashes estão em [ficheiros-primeira-entrega.csv](ficheiros-primeira-entrega.csv). O workflow executa todos os testes frontend e usa PostgreSQL QA na porta 55441, compatível com as guardas existentes. + +Para reverter esta entrega, comparar primeiro o hash atual com o hash final do manifesto privado `delivery-changes.json`. Para cada ficheiro que existia antes, recuperar os bytes da sua cópia `before/`; para ficheiros novos desta entrega, retirar apenas os identificados como novos após verificar alterações posteriores. Preservar os ficheiros e mudanças anteriores. Não utilizar `git reset --hard` ou `git restore` global, que apagariam trabalho preexistente. Regerar assets com o lockfile existente após restaurar o código. Não há rollback de dados ativos nesta entrega, porque não foram modificados. A base QA descartável pode ser mantida para revisão; uma limpeza posterior deve confirmar nome/porta e nunca abranger a base ativa. diff --git a/docs/investmoz/v2/primeiro-percurso-ativo.md b/docs/investmoz/v2/primeiro-percurso-ativo.md new file mode 100644 index 00000000..8b66626f --- /dev/null +++ b/docs/investmoz/v2/primeiro-percurso-ativo.md @@ -0,0 +1,55 @@ +# Primeiro percurso ativo — acompanhamento pendente + +8 de outubro de 2026. Não foi criado dossier ou pedido nesta execução. + +## Constatações por leitura + +A base ativa `investemoz:5432` mantém as duas migrations instaladas, num total de 78. Às 10:02 UTC existiam zero dossiers e zero pedidos do novo Balcão. A leitura foi realizada em transação `READ ONLY`, sem alterações de dados. + +A consulta detalhada corrigiu a interpretação do contador anterior: existem **dois registos publicados do mesmo serviço**, não dois serviços distintos: + +| Código | Versão publicada | Título | +| --------------------- | ---------------- | ------------------------------------------- | +| `project-preparation` | 1 | Preparar e submeter dossier de investimento | +| `project-preparation` | 2 | Preparar e submeter dossier de investimento | + +Não se apagou nem alterou qualquer versão. A escolha/reutilização da versão pela interface ainda precisa de observação na sessão autorizada. Evidência privada: `first-active-services.json` e `active-confirmation.json`, em `storage/app/private/investmoz-v2`. + +## Limitação da interface + +A ferramenta de navegação não apresentou browsers disponíveis. As tentativas de abrir Chrome e o navegador integrado devolveram `Browser is not available`. Assim, não foi possível abrir o painel, verificar a conta/sessão do utilizador ou observar serviços/dossiers pela interface. Não foram utilizados browsers QA, contas sintéticas, impersonação ou escritas SQL como alternativa. + +Painel a abrir na sessão do utilizador: `http://127.0.0.1:8000/balcao-virtual/painel`. É necessário disponibilizar essa sessão à capacidade de navegação, ou continuar os passos manualmente com o utilizador. Não solicitar nem registar senha, OTP ou cookies de sessão. + +## Dados e etapas pendentes + +O utilizador ainda não forneceu dados reais para o primeiro dossier/pedido. Para o serviço publicado, a definição exige na submissão nome do projeto, descrição/objetivo, moeda e valor do investimento. Localização/fonte, cronograma, empregos e financiamento são opcionais; fundamento é obrigatório apenas quando o investidor declarar regime especial. Pressupostos desconhecidos não serão inventados. + +| Etapa | Resultado | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Abrir painel e verificar conta | Pendente: navegador indisponível | +| Apresentar serviços na interface | Pendente; dois registos/versões confirmados apenas por leitura da base | +| Reutilizar/criar dossier adequado | Pendente: dados reais e interface; zero dossiers constatados nesta leitura | +| Preencher e guardar rascunho | Não executado | +| Sair/regressar e verificar recuperação | Não executado | +| Pré-visualização e confirmação do utilizador | Pendente; nenhuma submissão autorizada sobre conteúdo concreto | +| Submissão, protocolo, recibo, linha temporal e notificação | Não executado; nenhum resultado inventado | +| Encaminhamento ao responsável | Pendente: verificar responsável existente e autorizado no fluxo; nenhuma atribuição ou resposta criada | + +Os anexos continuam sujeitos ao scan real. ClamD permanecia indisponível na verificação anterior; não houve novo scan nem mudança de estado de ficheiros. Não se deve inferir segurança a partir de fixtures QA ou da instalação do módulo. A preparação sem anexos pode prosseguir quando houver sessão e dados reais. + +Guardar rascunho será executado apenas pela interface com os dados fornecidos. A submissão só será executada depois de apresentar o conteúdo concreto e receber a confirmação pedida pelo utilizador. + +## Investigação da ação do painel — 8 de outubro + +Página indicada pelo utilizador: `/balcao-virtual/painel`. O botão no código atual é **Preparar dossier**, não «Validar». Envia `POST /balcao-virtual/dossiers`; criar dossier não submete um pedido. Às **10:09:32 UTC**, a leitura ativa ainda encontrou **zero dossiers e zero pedidos**. Não há alteração de criação persistida para repetir. Essa ausência não demonstra, por si só, se a tentativa chegou ao servidor; não foi capturada uma resposta HTTP da sessão real, nem inferido um motivo específico para a tentativa do utilizador. + +A ação foi corrigida para apresentar progresso, impedir novo envio durante a operação com bloqueio imediato e manter a chave idempotente. Sucesso apresenta uma mensagem visível no dossier: foi guardado, a próxima ação é escolher serviço e preparar rascunho, nenhum pedido foi submetido. Falhas de validação mantêm os campos e mostram erros; conflito, falta de autorização, sessão expirada e indisponibilidade mostram motivo em alerta. Falha de ligação informa que o resultado não pôde ser confirmado e orienta consultar dossiers antes de repetir, preservando os dados. + +Testes exclusivamente em `investmoz_v2_20261008_test:55441`: **19 testes PHP, 287 asserções**, PHPStan, TypeScript, lint e build aprovados. O teste HTTP confirma persistência, redirecionamento, mensagem de sessão, recuperação e idempotência, com zero pedidos submetidos. Evidência privada: `panel-tests.txt`, `panel-phpstan.txt`, `panel-types.txt`, `panel-lint.txt`, `panel-build.txt`. Cópias anteriores dos dois ficheiros de produção: `panel-before`. + +Não foi repetida nenhuma escrita na base ativa. O novo feedback está no código local; a confirmação do percurso com dados reais continua pendente de execução do utilizador pela interface. ClamD e as restrições documentais mantêm-se. + +A validação automatizada no navegador QA passou, sem erros JavaScript: validação, HTTP 409 e falha de ligação preservaram o título; duplo clique durante o envio originou **um único POST de criação bem-sucedida**; confirmação e próxima ação ficaram visíveis; sair para o painel e reabrir recuperou o mesmo dossier persistido; zero pedidos submetidos. Evidência: `panel-browser-evidence.json`. O contador de POST dessa evidência refere-se à fase de sucesso, não às tentativas de erro do teste. + +Leitura ativa final às **10:13:50 UTC**: continuam zero dossiers e zero pedidos. O motivo concreto da tentativa real anterior permanece indeterminado sem captura da respetiva resposta na sessão do utilizador; não foi declarado como falha de consentimento ou de rede por inferência. diff --git a/docs/investmoz/zones/baseline.md b/docs/investmoz/zones/baseline.md index 5283799d..4a2f0bf4 100644 --- a/docs/investmoz/zones/baseline.md +++ b/docs/investmoz/zones/baseline.md @@ -4,16 +4,16 @@ Registo histórico da Fase 0. O proprietário autorizou depois a progressão aut ## Execução e resultados -| Verificação executada | Resultado | -| --- | --- | -| PHPUnit completo em PostgreSQL/PostGIS isolado | 138 testes, 1.463 verificações, todos aprovados; 27,943 s | -| PHPStan completo | Zero erros | -| TypeScript `--noEmit` | Aprovado | -| Lint páginas investment/portfolio e PortalNavigation | Aprovado | -| Pint do script de inventário | Primeira verificação encontrou estilo; formatado; verificação final aprovada, assim como `php -l` | -| Build Vite | Aprovado, 2.354 módulos, 11,78 s; aviso de dependência opcional fontaine, sem falha | -| Inventário agregado | Executado em transacção READ ONLY/REPEATABLE READ; 93 tabelas; JSON adjacente | -| Backup da base | SHA-256 conferido; manifesto regista restauro verificado em 93 tabelas | +| Verificação executada | Resultado | +| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| PHPUnit completo em PostgreSQL/PostGIS isolado | 138 testes, 1.463 verificações, todos aprovados; 27,943 s | +| PHPStan completo | Zero erros | +| TypeScript `--noEmit` | Aprovado | +| Lint páginas investment/portfolio e PortalNavigation | Aprovado | +| Pint do script de inventário | Primeira verificação encontrou estilo; formatado; verificação final aprovada, assim como `php -l` | +| Build Vite | Aprovado, 2.354 módulos, 11,78 s; aviso de dependência opcional fontaine, sem falha | +| Inventário agregado | Executado em transacção READ ONLY/REPEATABLE READ; 93 tabelas; JSON adjacente | +| Backup da base | SHA-256 conferido; manifesto regista restauro verificado em 93 tabelas | Backup privado da base: `storage/app/private/portfolio-version-backup-20260928-011346/`. O backup antecede a migration de versão editorial; a reconciliação existente documenta apenas `migrations` 34→35. Backup completo anterior da aplicação: `C:/laragon/backups/portal-investidor-20260927-101224/RESTAURO.md`. Não publicar estes backups nem as configurações privadas. Esta fase não fez alterações de esquema ou conteúdo de negócio. @@ -63,15 +63,15 @@ Não havia `.git`. Foi inicializado repositório local na branch `feature/zee-zf ## Checklist de gate -| Condição | Estado | -| --- | --- | -| Código acessível e versões/comandos identificados | Sim | -| Backup existente com restauro documentado e checksum conferido | Sim | -| Baseline de testes reproduzido | Sim | -| Diagnóstico, matriz de decisões, ADR e backlog preparados | Sim | -| Escopo piloto aprovado pelo proprietário | Pendente — proposta no README | -| Identidade Git e primeiro commit | Pendente | -| Transição de permissões e responsáveis de validação | Pendente | -| Autorização para iniciar Fase 1 | A aguardar decisão no gate | +| Condição | Estado | +| -------------------------------------------------------------- | ----------------------------- | +| Código acessível e versões/comandos identificados | Sim | +| Backup existente com restauro documentado e checksum conferido | Sim | +| Baseline de testes reproduzido | Sim | +| Diagnóstico, matriz de decisões, ADR e backlog preparados | Sim | +| Escopo piloto aprovado pelo proprietário | Pendente — proposta no README | +| Identidade Git e primeiro commit | Pendente | +| Transição de permissões e responsáveis de validação | Pendente | +| Autorização para iniciar Fase 1 | A aguardar decisão no gate | Recomendação: **Go condicionado**. Próxima fase proposta: ER/dicionário, matriz jurídica, identidade canónica e relações tipadas, migrations aditivas e testes GIS/integridade/reversão em base isolada. Não iniciada. diff --git a/docs/investmoz/zones/browser-validation.json b/docs/investmoz/zones/browser-validation.json index 5917f0f2..1d012522 100644 --- a/docs/investmoz/zones/browser-validation.json +++ b/docs/investmoz/zones/browser-validation.json @@ -1,101 +1,101 @@ { - "date": "2026-09-28T06:31:57.045Z", - "scope": "Public pages and empty states only; no institutional workflow/UAT claim", - "observations": [ - { - "path": "/zonas?category=zee", - "width": 390, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/zonas?category=zfi", - "width": 390, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/zonas?category=industrial_park", - "width": 390, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/oportunidades", - "width": 390, - "title": "Catálogo de investimento", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/oportunidades?view=combined", - "width": 390, - "title": "Catálogo de investimento", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/zonas?category=zee", - "width": 1440, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/zonas?category=zfi", - "width": 1440, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/zonas?category=industrial_park", - "width": 1440, - "title": "ZEE, ZFI e parques industriais", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/oportunidades", - "width": 1440, - "title": "Catálogo de investimento", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "path": "/oportunidades?view=combined", - "width": 1440, - "title": "Catálogo de investimento", - "overflow": false, - "breadcrumb": true, - "support": true, - "dimensions": true - }, - { - "scenario": "map_request_blocked", - "fallback_list": true - } - ], - "errors": [] -} \ No newline at end of file + "date": "2026-09-28T06:31:57.045Z", + "scope": "Public pages and empty states only; no institutional workflow/UAT claim", + "observations": [ + { + "path": "/zonas?category=zee", + "width": 390, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/zonas?category=zfi", + "width": 390, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/zonas?category=industrial_park", + "width": 390, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/oportunidades", + "width": 390, + "title": "Catálogo de investimento", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/oportunidades?view=combined", + "width": 390, + "title": "Catálogo de investimento", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/zonas?category=zee", + "width": 1440, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/zonas?category=zfi", + "width": 1440, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/zonas?category=industrial_park", + "width": 1440, + "title": "ZEE, ZFI e parques industriais", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/oportunidades", + "width": 1440, + "title": "Catálogo de investimento", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "path": "/oportunidades?view=combined", + "width": 1440, + "title": "Catálogo de investimento", + "overflow": false, + "breadcrumb": true, + "support": true, + "dimensions": true + }, + { + "scenario": "map_request_blocked", + "fallback_list": true + } + ], + "errors": [] +} diff --git a/docs/investmoz/zones/data-dictionary.md b/docs/investmoz/zones/data-dictionary.md index cda3315d..1bc06337 100644 --- a/docs/investmoz/zones/data-dictionary.md +++ b/docs/investmoz/zones/data-dictionary.md @@ -1,17 +1,17 @@ # Dicionário do núcleo implementado -| Tabela | Identidade e relações | Regras | -| --- | --- | --- | -| investment_items | UUID; código único; tipo; título; versão | Representação canónica interna; não duplica a oportunidade pública | -| investment_item_opportunities | item UUID → opportunity bigint único | Correspondência explícita; conserva ID, slug e URLs existentes | -| investment_item_projects | item UUID → project UUID único | Correspondência só com evidência; não inferida por nome | -| investment_zones | UUID; código único; operador FK organizations; fonte FK | Categoria zee/zfi/industrial_park/pending; publicação independente, editor/revisor, conteúdo PT/EN, versão; soft-delete | -| investment_legal_sources | UUID; proprietário e validador FK users | Diploma/artigo/URL/entidade/vigência; pending/validated/revoked; outra pessoa valida; alteração documental requer nova referência | -| investment_zone_links | item ↔ zona, par único | Papel, associação, prioridade, principal/secundária, evidência, período, fonte jurídica opcional, editor/validador e versão | -| investment_zone_relations | zona ↔ exactamente um cluster/corredor/projecto | FKs explícitas e índice único por dimensão; múltiplas zonas por âncora; mesmas regras temporais e de validação | -| investment_zone_geometries | zona ↔ geo_geometries | Finalidade, fonte, entidade, data, permissão de publicação, editor/revisor GIS; estatuto não inferido por localização | -| investment_zone_revisions | zona/versão únicos | Snapshot encriptado, SHA-256, actor/acção/data; trigger recusa UPDATE/DELETE | -| investment_submission_zones | submission UUID → zona UUID | Versão consultada e finalidade; origem preservada no protocolo, sem listas relacionais em JSON | +| Tabela | Identidade e relações | Regras | +| ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| investment_items | UUID; código único; tipo; título; versão | Representação canónica interna; não duplica a oportunidade pública | +| investment_item_opportunities | item UUID → opportunity bigint único | Correspondência explícita; conserva ID, slug e URLs existentes | +| investment_item_projects | item UUID → project UUID único | Correspondência só com evidência; não inferida por nome | +| investment_zones | UUID; código único; operador FK organizations; fonte FK | Categoria zee/zfi/industrial_park/pending; publicação independente, editor/revisor, conteúdo PT/EN, versão; soft-delete | +| investment_legal_sources | UUID; proprietário e validador FK users | Diploma/artigo/URL/entidade/vigência; pending/validated/revoked; outra pessoa valida; alteração documental requer nova referência | +| investment_zone_links | item ↔ zona, par único | Papel, associação, prioridade, principal/secundária, evidência, período, fonte jurídica opcional, editor/validador e versão | +| investment_zone_relations | zona ↔ exactamente um cluster/corredor/projecto | FKs explícitas e índice único por dimensão; múltiplas zonas por âncora; mesmas regras temporais e de validação | +| investment_zone_geometries | zona ↔ geo_geometries | Finalidade, fonte, entidade, data, permissão de publicação, editor/revisor GIS; estatuto não inferido por localização | +| investment_zone_revisions | zona/versão únicos | Snapshot encriptado, SHA-256, actor/acção/data; trigger recusa UPDATE/DELETE | +| investment_submission_zones | submission UUID → zona UUID | Versão consultada e finalidade; origem preservada no protocolo, sem listas relacionais em JSON | Campos jurídicos e coordenadas não têm valores reais predefinidos. As migrations não semeiam estatutos, percentagens ou entidades. As restrições de geometria existentes asseguram 2D, SRID 4326, não vazio, tipo correcto, validade topológica e limites terrestres. Todas as novas FKs usam restrição de eliminação. diff --git a/docs/investmoz/zones/diagnosis-and-adr.md b/docs/investmoz/zones/diagnosis-and-adr.md index 1459086e..3a398ec9 100644 --- a/docs/investmoz/zones/diagnosis-and-adr.md +++ b/docs/investmoz/zones/diagnosis-and-adr.md @@ -2,18 +2,18 @@ ## Inventário confirmado -| Área | Implementação e convenções | Lacuna para o pedido | -| --- | --- | --- | -| Backend | Laravel 13.30.1; PHP de validação 8.5.11; Inertia Laravel 3.3.3; Fortify 1.39.0 | PHP do PATH não é o executável validado com PostgreSQL | -| Frontend | React 19.2.8; Inertia React 3.7.0; TypeScript 5.9.3; Vite 8.2.2; Node 24.20.0 | PT/EN integral, mapa territorial, lista–mapa, testes assistivos e estados de falha incompletos | -| Dados | PostgreSQL 17.11/PostGIS 3.6.2; 93 tabelas; 35 migrations | Sem mestre ZEE/ZFI/parques ou regras jurídicas versionadas | -| Catálogo | `Opportunity` bigint, slug/reference, filtros em `InvestmentCatalogue`, contexto M:N em `InvestmentContextController` | Não representa ainda todos os tipos canónicos; pivots precisam fonte, vigência, papel e validação | -| Território | UUID em `projects`, `economic_clusters`, `development_corridors`; dossiers públicos separados | Não confundir dossier editorial com investimento; correspondência projecto–oportunidade por reconciliar | -| GIS | `geo_locations`, `geo_geometries`, SRID 4326, checks de tipo/validade/vazio/coordenadas | Zero geometrias locais; falta proveniência oficial, perímetro versionado e pesquisa espacial completa | -| CRM | `CrmIntake`, conversão para lead, outbox/inbox; `app/Integrations/SuiteCrm/SuiteCrmClient.php` e testes com simulação | Integração real não homologada; não executar chamadas externas nesta fase | -| Autenticação | Sessão, email verificado, gate `manage-portal`, suporte Fortify/passkeys | Gate por lista de gestores não substitui RBAC por função/atributo nem MFA obrigatório | -| Privacidade | Payloads e revisões de manifestações encriptados; ficheiros privados; middleware no-store | Retenção, segregação de funções e revisão editorial territorial integral pendentes | -| IA | Pesquisa lexical/rules interna com fontes e suspensão | Sem geração/RAG; achados R2–R5 da revisão continuam abertos | +| Área | Implementação e convenções | Lacuna para o pedido | +| ------------ | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| Backend | Laravel 13.30.1; PHP de validação 8.5.11; Inertia Laravel 3.3.3; Fortify 1.39.0 | PHP do PATH não é o executável validado com PostgreSQL | +| Frontend | React 19.2.8; Inertia React 3.7.0; TypeScript 5.9.3; Vite 8.2.2; Node 24.20.0 | PT/EN integral, mapa territorial, lista–mapa, testes assistivos e estados de falha incompletos | +| Dados | PostgreSQL 17.11/PostGIS 3.6.2; 93 tabelas; 35 migrations | Sem mestre ZEE/ZFI/parques ou regras jurídicas versionadas | +| Catálogo | `Opportunity` bigint, slug/reference, filtros em `InvestmentCatalogue`, contexto M:N em `InvestmentContextController` | Não representa ainda todos os tipos canónicos; pivots precisam fonte, vigência, papel e validação | +| Território | UUID em `projects`, `economic_clusters`, `development_corridors`; dossiers públicos separados | Não confundir dossier editorial com investimento; correspondência projecto–oportunidade por reconciliar | +| GIS | `geo_locations`, `geo_geometries`, SRID 4326, checks de tipo/validade/vazio/coordenadas | Zero geometrias locais; falta proveniência oficial, perímetro versionado e pesquisa espacial completa | +| CRM | `CrmIntake`, conversão para lead, outbox/inbox; `app/Integrations/SuiteCrm/SuiteCrmClient.php` e testes com simulação | Integração real não homologada; não executar chamadas externas nesta fase | +| Autenticação | Sessão, email verificado, gate `manage-portal`, suporte Fortify/passkeys | Gate por lista de gestores não substitui RBAC por função/atributo nem MFA obrigatório | +| Privacidade | Payloads e revisões de manifestações encriptados; ficheiros privados; middleware no-store | Retenção, segregação de funções e revisão editorial territorial integral pendentes | +| IA | Pesquisa lexical/rules interna com fontes e suspensão | Sem geração/RAG; achados R2–R5 da revisão continuam abertos | Árvore funcional: `app/Models`, `app/Services`, `app/Http/Controllers`, `app/Integrations`, `database/migrations`, `database/seeders`, `resources/js/pages/{investment,portfolio,management}`, `routes/{web,investment}.php`, `tests/{Feature,Unit}`, `deploy/{linux,suitecrm}`, `docs/investmoz`. @@ -23,14 +23,14 @@ Inventário local: seis oportunidades; 16 projectos (não confundir com as 15 â ## Conservar–Melhorar–Substituir–Descontinuar -| Decisão | Objecto | Tratamento | -| --- | --- | --- | -| Conservar | IDs, slugs, dossiers, pedidos, consentimentos, anexos, favoritos e API v1 | Adaptadores e testes de contrato; sem apagar ou reclassificar em massa | -| Melhorar | Relações, cadastro GIS, fontes, auditoria, procura e páginas | FKs explícitas, temporalidade, validação, índices e permissões | -| Substituir gradualmente | Gate único de gestor para actos críticos | Políticas separadas e revisão independente; sem conceder privilégios por registo público | -| Substituir gradualmente | Mapa de pontos como única vista geográfica | Camadas autorizadas e lista sempre utilizável quando mapa falhar | -| Descontinuar após migração | Escritas paralelas da mesma identidade | Só após correspondência aprovada e reconciliação; nenhuma removida agora | -| Não importar | Estatutos, perímetros e benefícios apenas citados no prompt | Manter pendência documental, sem inferir regime jurídico | +| Decisão | Objecto | Tratamento | +| -------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| Conservar | IDs, slugs, dossiers, pedidos, consentimentos, anexos, favoritos e API v1 | Adaptadores e testes de contrato; sem apagar ou reclassificar em massa | +| Melhorar | Relações, cadastro GIS, fontes, auditoria, procura e páginas | FKs explícitas, temporalidade, validação, índices e permissões | +| Substituir gradualmente | Gate único de gestor para actos críticos | Políticas separadas e revisão independente; sem conceder privilégios por registo público | +| Substituir gradualmente | Mapa de pontos como única vista geográfica | Camadas autorizadas e lista sempre utilizável quando mapa falhar | +| Descontinuar após migração | Escritas paralelas da mesma identidade | Só após correspondência aprovada e reconciliação; nenhuma removida agora | +| Não importar | Estatutos, perímetros e benefícios apenas citados no prompt | Manter pendência documental, sem inferir regime jurídico | ## Decisão arquitectural proposta diff --git a/docs/investmoz/zones/implementation-status.md b/docs/investmoz/zones/implementation-status.md index aad396ac..c05d836c 100644 --- a/docs/investmoz/zones/implementation-status.md +++ b/docs/investmoz/zones/implementation-status.md @@ -26,15 +26,15 @@ Terceiro backup: `zones-backup-20260928-061527/`, 102 tabelas restauradas/confer ## Limites actuais por fase -| Fase | Estado factual | Trabalho ainda necessário | -| --- | --- | --- | -| 0 | Baseline e plano executados | Identidade Git para commits; não configurada em nome do utilizador | -| 1 | Núcleo canónico/legal/GIS aditivo implementado | Modelo completo de actividades, benefícios, obrigações, instalações, lotes e indicadores; matriz jurídica por artigo; correspondências projecto–oportunidade reconciliadas | -| 2 | APIs e administração do núcleo implementadas | Workflows institucionais completos, histórico de todas as entidades, atribuição nominal de funções, MFA de sessão e testes de contrato exaustivos | -| 3 | Filtros das zonas integrados com resultados sem duplicação | Descoberta indirecta por elos/nós/influência, ranking/sinónimos, telemetria e benchmark representativo | -| 4 | Cadastro e fichas com conteúdo PT/EN, mapa esquemático, lista–mapa, filtros/vista no URL e recuperação da falha do mapa | Interface integral EN, acessibilidade assistiva e E2E de todas as jornadas | -| 5 | Interesse/dossier/reunião assistidos com origem zona/versão/finalidade no protocolo e CRM interno | Origem completa de filtros, SLA parametrizado e integração externa real formalmente autorizada | -| 6 | Backups/restauros, dry-run, seis correspondências canónicas e reconciliação executados | UAT, carga, formação real, recuperação pontual e piloto com dados oficiais | -| 7 | Não iniciada | Decisão de produção, suporte e requisitos operacionais | +| Fase | Estado factual | Trabalho ainda necessário | +| ---- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Baseline e plano executados | Identidade Git para commits; não configurada em nome do utilizador | +| 1 | Núcleo canónico/legal/GIS aditivo implementado | Modelo completo de actividades, benefícios, obrigações, instalações, lotes e indicadores; matriz jurídica por artigo; correspondências projecto–oportunidade reconciliadas | +| 2 | APIs e administração do núcleo implementadas | Workflows institucionais completos, histórico de todas as entidades, atribuição nominal de funções, MFA de sessão e testes de contrato exaustivos | +| 3 | Filtros das zonas integrados com resultados sem duplicação | Descoberta indirecta por elos/nós/influência, ranking/sinónimos, telemetria e benchmark representativo | +| 4 | Cadastro e fichas com conteúdo PT/EN, mapa esquemático, lista–mapa, filtros/vista no URL e recuperação da falha do mapa | Interface integral EN, acessibilidade assistiva e E2E de todas as jornadas | +| 5 | Interesse/dossier/reunião assistidos com origem zona/versão/finalidade no protocolo e CRM interno | Origem completa de filtros, SLA parametrizado e integração externa real formalmente autorizada | +| 6 | Backups/restauros, dry-run, seis correspondências canónicas e reconciliação executados | UAT, carga, formação real, recuperação pontual e piloto com dados oficiais | +| 7 | Não iniciada | Decisão de produção, suporte e requisitos operacionais | Não declarar a implementação integral concluída com base nesta entrega. Zero geometrias e zonas oficiais estão cadastradas localmente; o estado vazio é intencional até haver documentos e responsáveis. Configurar `INVESTMOZ_ZONE_LEGAL_REVIEWERS`, `INVESTMOZ_ZONE_GIS_REVIEWERS`, `INVESTMOZ_ZONE_PUBLISHERS` com emails de gestores já autorizados, após designação. Estas listas não atribuem competência legal por si próprias. diff --git a/docs/investmoz/zones/industrial-parks-reconciliation-2026-10-04.md b/docs/investmoz/zones/industrial-parks-reconciliation-2026-10-04.md index 112c7cac..4202cff3 100644 --- a/docs/investmoz/zones/industrial-parks-reconciliation-2026-10-04.md +++ b/docs/investmoz/zones/industrial-parks-reconciliation-2026-10-04.md @@ -8,19 +8,19 @@ A matriz estruturada está em `database/data/industrial-parks-reconciliation.jso ## Reconciliação dos onze itens -| Item | Resultado da comparação | O que permanece pendente | -|---|---|---| -| Beluluane | MozParks publica 700 ha prontos para arrendamento. APIEX descreve complexo de cerca de 780 ha, composto por parque de 600 ha e Mozal de 180 ha; indica ZFI de 55 ha. São âmbitos potencialmente diferentes. | Limites e sobreposição cartográfica; disponibilidade por lote. Não somar as áreas nem converter enfoque industrial em actividade autorizada. Parque e ZFI mantêm registos separados. | -| Topuito | MozParks publica 215 ha e identifica Topuito Industrial Park. Notícia de Agosto de 2026 relata início da construção do SME Village. Decreto 57/2023 cria a ZFI com 65 ha. | Distinguir parque, construção do SME Village e perímetro jurídico da ZFI. Não inferir disponibilidade integral. O território mestre não contém Larde; manter Nampula provincial até resolver a referência territorial. | -| Munhava | SDUBeira descreve desenvolvimento faseado, 900 ha no resumo, destaque de 990 ha e piloto de 50 ha. | Os 44–60 ha do documento não foram corroborados. Resolver a divergência interna, obter perímetro/plano técnico e confirmar disponibilidade. Sectores são alvos declarados. | -| Montepuez | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Fornecedores mineiros, transformação, serviços e PME são enfoques do documento, não licenças. | -| Balama | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | -| Ancuabe | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | -| Palma | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | -| Selva / Manica Parks | MozParks confirma o lançamento da iniciativa provincial Manica Parks por memorando em Setembro de 2026. | Não confirma parque individual em Selva/Vanduzi, construção, operação ou operador. Não criar ficha localizada em Selva sem fonte específica. Actividades listadas são enfoques declarados. | -| Mocuba ZFI | APIEX confirma 50,58 ha e mobilização de recursos e operador. O documento lista actividades potenciais. | Diploma primário, limites e autorização sectorial. A ZFI permanece separada da ZEE de Mocuba. | -| Revúbuè ZFI | APIEX reporta 4 456 ha, ausência de operador e um projecto aprovado. | Validar Decreto 41/2016 e perímetro. A referência ao projecto aprovado não é oportunidade de investimento publicada; sectores são enfoque, não autorização. Um registo cobre Chiuta e Moatize. | -| Minhewene ZFI | O documento diz que não encontrou ficha APIEX acessível. Na verificação actual, a página APIEX estava acessível e reportava Decreto 51/2011, Nacala, 350 ha e mobilização de recursos/operador. | Resolver a divergência documental de acesso; confirmar diploma primário, localização cadastral e estado actual. Página institucional não prova operacionalização. | +| Item | Resultado da comparação | O que permanece pendente | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Beluluane | MozParks publica 700 ha prontos para arrendamento. APIEX descreve complexo de cerca de 780 ha, composto por parque de 600 ha e Mozal de 180 ha; indica ZFI de 55 ha. São âmbitos potencialmente diferentes. | Limites e sobreposição cartográfica; disponibilidade por lote. Não somar as áreas nem converter enfoque industrial em actividade autorizada. Parque e ZFI mantêm registos separados. | +| Topuito | MozParks publica 215 ha e identifica Topuito Industrial Park. Notícia de Agosto de 2026 relata início da construção do SME Village. Decreto 57/2023 cria a ZFI com 65 ha. | Distinguir parque, construção do SME Village e perímetro jurídico da ZFI. Não inferir disponibilidade integral. O território mestre não contém Larde; manter Nampula provincial até resolver a referência territorial. | +| Munhava | SDUBeira descreve desenvolvimento faseado, 900 ha no resumo, destaque de 990 ha e piloto de 50 ha. | Os 44–60 ha do documento não foram corroborados. Resolver a divergência interna, obter perímetro/plano técnico e confirmar disponibilidade. Sectores são alvos declarados. | +| Montepuez | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Fornecedores mineiros, transformação, serviços e PME são enfoques do documento, não licenças. | +| Balama | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | +| Ancuabe | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | +| Palma | MozParks confirma parque da iniciativa Cabo Delgado em desenvolvimento no distrito. | Área, regime, actividade instalada/autorizada e disponibilidade. Sectores declarados não são autorizações. | +| Selva / Manica Parks | MozParks confirma o lançamento da iniciativa provincial Manica Parks por memorando em Setembro de 2026. | Não confirma parque individual em Selva/Vanduzi, construção, operação ou operador. Não criar ficha localizada em Selva sem fonte específica. Actividades listadas são enfoques declarados. | +| Mocuba ZFI | APIEX confirma 50,58 ha e mobilização de recursos e operador. O documento lista actividades potenciais. | Diploma primário, limites e autorização sectorial. A ZFI permanece separada da ZEE de Mocuba. | +| Revúbuè ZFI | APIEX reporta 4 456 ha, ausência de operador e um projecto aprovado. | Validar Decreto 41/2016 e perímetro. A referência ao projecto aprovado não é oportunidade de investimento publicada; sectores são enfoque, não autorização. Um registo cobre Chiuta e Moatize. | +| Minhewene ZFI | O documento diz que não encontrou ficha APIEX acessível. Na verificação actual, a página APIEX estava acessível e reportava Decreto 51/2011, Nacala, 350 ha e mobilização de recursos/operador. | Resolver a divergência documental de acesso; confirmar diploma primário, localização cadastral e estado actual. Página institucional não prova operacionalização. | ## Legislação e uso no portal diff --git a/docs/investmoz/zones/integration-2026-10-04.md b/docs/investmoz/zones/integration-2026-10-04.md index c6289d35..d6cde143 100644 --- a/docs/investmoz/zones/integration-2026-10-04.md +++ b/docs/investmoz/zones/integration-2026-10-04.md @@ -20,15 +20,15 @@ Não se aplicaram as restantes migrations pendentes. Antes da alteração foi gu O ensaio a 4 de Outubro resolveu todos os nomes abaixo para referências territoriais existentes. As relações ficam por rever antes de publicação. -| Código | Categoria / regime | Território resolvido | Área da fonte | Estado editorial / operacional | -| --- | --- | --- | --- | --- | -| `MOZ-BELULUANE-PARK` | parque industrial / geral | Posto Administrativo Matola Rio, sob Boane | cerca de 780 ha do conjunto | rascunho / não confirmado | -| `MOZ-BELULUANE-ZFI` | zona franca industrial / ZFI | Matola Rio, componente distinta do parque | cerca de 55 ha | rascunho / não confirmado | -| `MOZ-CRUSSE-JAMALI-ZEE` | área de turismo integrado / ZEE | Posto Administrativo Matibane, Mossuril | 1.750 ha | rascunho / não confirmado | -| `MOZ-MANGA-MUNGASSA-ZEE` | zona económica / ZEE | Cidade da Beira | 217 ha | rascunho / não confirmado | -| `MOZ-MOCUBA-ZEE` | zona económica / ZEE | distrito de Mocuba e PA Munhamade, Lugela | 10.727 km² | rascunho / não confirmado | -| `MOZ-MOCUBA-ZFI` | zona franca industrial / ZFI | PA Cidade de Mocuba | 50,58 ha | rascunho / não confirmado | -| `MOZ-NACALA-ZEE` | zona económica / ZEE | distritos de Nacala-Porto e Nacala-Velha | 1.539 km² | rascunho / não confirmado | +| Código | Categoria / regime | Território resolvido | Área da fonte | Estado editorial / operacional | +| ------------------------ | ------------------------------- | ------------------------------------------ | --------------------------- | ------------------------------ | +| `MOZ-BELULUANE-PARK` | parque industrial / geral | Posto Administrativo Matola Rio, sob Boane | cerca de 780 ha do conjunto | rascunho / não confirmado | +| `MOZ-BELULUANE-ZFI` | zona franca industrial / ZFI | Matola Rio, componente distinta do parque | cerca de 55 ha | rascunho / não confirmado | +| `MOZ-CRUSSE-JAMALI-ZEE` | área de turismo integrado / ZEE | Posto Administrativo Matibane, Mossuril | 1.750 ha | rascunho / não confirmado | +| `MOZ-MANGA-MUNGASSA-ZEE` | zona económica / ZEE | Cidade da Beira | 217 ha | rascunho / não confirmado | +| `MOZ-MOCUBA-ZEE` | zona económica / ZEE | distrito de Mocuba e PA Munhamade, Lugela | 10.727 km² | rascunho / não confirmado | +| `MOZ-MOCUBA-ZFI` | zona franca industrial / ZFI | PA Cidade de Mocuba | 50,58 ha | rascunho / não confirmado | +| `MOZ-NACALA-ZEE` | zona económica / ZEE | distritos de Nacala-Porto e Nacala-Velha | 1.539 km² | rascunho / não confirmado | Decisões de proveniência: diff --git a/docs/investmoz/zones/legal-matrix.md b/docs/investmoz/zones/legal-matrix.md index a8b7e316..84705c2a 100644 --- a/docs/investmoz/zones/legal-matrix.md +++ b/docs/investmoz/zones/legal-matrix.md @@ -2,11 +2,11 @@ Consulta em 28/09/2026. As referências abaixo são pontos de partida para conferência por quem detém competência jurídica; não são regras operacionais validadas nem autorizam publicação de taxas, benefícios ou certificados. -| Referência | Fonte encontrada | Estado / tratamento | -| --- | --- | --- | -| Lei 8/2023 e Decreto 8/2024 indicados no pedido | [Legislação APIEX](https://pt.apiex.gov.mz/legislacao/) e [compilação de legislação de investimentos](https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf) | Conferir texto, artigo, alterações posteriores, vigência e sujeito antes de registar regra validada | -| Cadastro e regime ZEE | [Página institucional de ZEE](https://apiex.gov.mz/zona-economica-especial/) | Informação de orientação; obter diploma e perímetro por zona; não converter percentagens da página em parâmetros do simulador | -| Regime ZFI | [Página institucional ZFI](https://apiex.gov.mz/ifz/industrial-free-zone/) | Não confirma título individual de Beluluane, Locone ou Minhuene | +| Referência | Fonte encontrada | Estado / tratamento | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Lei 8/2023 e Decreto 8/2024 indicados no pedido | [Legislação APIEX](https://pt.apiex.gov.mz/legislacao/) e [compilação de legislação de investimentos](https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf) | Conferir texto, artigo, alterações posteriores, vigência e sujeito antes de registar regra validada | +| Cadastro e regime ZEE | [Página institucional de ZEE](https://apiex.gov.mz/zona-economica-especial/) | Informação de orientação; obter diploma e perímetro por zona; não converter percentagens da página em parâmetros do simulador | +| Regime ZFI | [Página institucional ZFI](https://apiex.gov.mz/ifz/industrial-free-zone/) | Não confirma título individual de Beluluane, Locone ou Minhuene | Nenhuma destas fontes foi importada como validada. O sistema exige referência HTTPS, diploma, artigo, entidade, início/fim de vigência, proprietário e revisão por pessoa distinta. A verificação de formulário não certifica autenticidade nem competência legal. Substituições devem criar nova referência; revogação retira imediatamente as zonas e associações dependentes da consulta pública. diff --git a/docs/investmoz/zones/openapi.json b/docs/investmoz/zones/openapi.json index fc9fc3a6..8dcfb914 100644 --- a/docs/investmoz/zones/openapi.json +++ b/docs/investmoz/zones/openapi.json @@ -1,2463 +1,2409 @@ { - "openapi": "3.0.3", - "info": { - "title": "InvestMoz zone foundation API", - "version": "2026-09-28", - "description": "Implemented local routes only. Session cookies and CSRF; no bearer protocol invented. Admin: verified manager with confirmed MFA; legal/GIS/publish actions also require configured designated role. 30 requests/minute for management; public API uses application throttle. Approval in the portal is documentary review, not an administrative act. API v1 remains compatible and adds optional zee_id/zfi_id/park_id filters. Response schemas are deliberately open pending full contract typing." - }, - "paths": { - "/api/v2/investment/admin/legal-sources": { - "get": { - "operationId": "InvestmentLegalSourceController_index_0", - "summary": "InvestmentLegalSourceController@index", - "parameters": [], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - }, - "post": { - "operationId": "InvestmentLegalSourceController_store_1", - "summary": "InvestmentLegalSourceController@store", - "parameters": [], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LegalSource" - } - } - } - } - } + "openapi": "3.0.3", + "info": { + "title": "InvestMoz zone foundation API", + "version": "2026-09-28", + "description": "Implemented local routes only. Session cookies and CSRF; no bearer protocol invented. Admin: verified manager with confirmed MFA; legal/GIS/publish actions also require configured designated role. 30 requests/minute for management; public API uses application throttle. Approval in the portal is documentary review, not an administrative act. API v1 remains compatible and adds optional zee_id/zfi_id/park_id filters. Response schemas are deliberately open pending full contract typing." }, - "/api/v2/investment/admin/legal-sources/{id}/{action}": { - "post": { - "operationId": "InvestmentLegalSourceController_transition_2", - "summary": "InvestmentLegalSourceController@transition", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "validate", - "revoke" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" + "paths": { + "/api/v2/investment/admin/legal-sources": { + "get": { + "operationId": "InvestmentLegalSourceController_index_0", + "summary": "InvestmentLegalSourceController@index", + "parameters": [], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + }, + "post": { + "operationId": "InvestmentLegalSourceController_store_1", + "summary": "InvestmentLegalSourceController@store", + "parameters": [], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LegalSource" + } + } + } } - } } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } + "/api/v2/investment/admin/legal-sources/{id}/{action}": { + "post": { + "operationId": "InvestmentLegalSourceController_transition_2", + "summary": "InvestmentLegalSourceController@transition", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": ["validate", "revoke"] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "/api/v2/investment/admin/zone-attachments/{id}/{action}": { + "post": { + "operationId": "InvestmentZoneAttachmentController_review_3", + "summary": "InvestmentZoneAttachmentController@review", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "review", + "select-public", + "withdraw-public" + ] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version_no": { + "type": "integer", + "minimum": 1 + }, + "decision": { + "type": "string", + "enum": ["reviewed", "rejected"] + }, + "rights_status": { + "type": "string", + "enum": [ + "pending", + "confirmed", + "restricted" + ] + }, + "rights_basis": { + "type": "string", + "nullable": true + }, + "personal_data_status": { + "type": "string", + "enum": ["none", "present", "unknown"] + } + }, + "required": ["version_no"] + } + } + } + } + } + }, + "/api/v2/investment/admin/zone-geometries/{id}/{action}": { + "post": { + "operationId": "InvestmentZoneGeometryController_review_4", + "summary": "InvestmentZoneGeometryController@review", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": ["validate", "revoke"] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "/api/v2/investment/admin/zone-links/{id}/{action}": { + "post": { + "operationId": "InvestmentZoneLinkController_transition_5", + "summary": "InvestmentZoneLinkController@transition", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": ["validate", "revoke"] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "/api/v2/investment/admin/zone-locations/{id}/validate": { + "post": { + "operationId": "InvestmentZoneController_validateLocation_6", + "summary": "InvestmentZoneController@validateLocation", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Zone" + } + } + } + } + } + }, + "/api/v2/investment/admin/zone-relations/{id}/{action}": { + "post": { + "operationId": "InvestmentZoneRelationController_review_7", + "summary": "InvestmentZoneRelationController@review", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": ["validate", "revoke"] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones": { + "get": { + "operationId": "InvestmentZoneController_index_8", + "summary": "InvestmentZoneController@index", + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "category", + "in": "query", + "schema": { + "type": "string", + "enum": ["zee", "zfi", "industrial_park"] + } + }, + { + "name": "physical_category", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "legal_regime", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "development_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "availability_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "activity", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "locality_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + }, + "post": { + "operationId": "InvestmentZoneController_save_9", + "summary": "InvestmentZoneController@save", + "parameters": [], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Zone" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}": { + "get": { + "operationId": "InvestmentZoneController_show_10", + "summary": "InvestmentZoneController@show", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + }, + "put": { + "operationId": "InvestmentZoneController_save_11", + "summary": "InvestmentZoneController@save", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Zone" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/attachments": { + "post": { + "operationId": "InvestmentZoneAttachmentController_store_12", + "summary": "InvestmentZoneAttachmentController@store", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + }, + "kind": { + "type": "string", + "enum": [ + "legal_instrument", + "map", + "photo", + "study", + "other" + ] + }, + "caption": { + "type": "string", + "maxLength": 255 + }, + "source_reference": { + "type": "string", + "minLength": 5, + "maxLength": 2000 + }, + "source_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "rights_status": { + "type": "string", + "enum": [ + "pending", + "confirmed", + "restricted" + ] + }, + "rights_basis": { + "type": "string", + "nullable": true + } + }, + "required": [ + "file", + "kind", + "caption", + "source_reference", + "rights_status" + ] + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/geometries": { + "post": { + "operationId": "InvestmentZoneGeometryController_store_13", + "summary": "InvestmentZoneGeometryController@store", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Geometry" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/links": { + "post": { + "operationId": "InvestmentZoneLinkController_store_14", + "summary": "InvestmentZoneLinkController@store", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Link" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/relations": { + "post": { + "operationId": "InvestmentZoneRelationController_store_15", + "summary": "InvestmentZoneRelationController@store", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "201": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Relation" + } + } + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/revisions": { + "get": { + "operationId": "InvestmentZoneController_revisions_16", + "summary": "InvestmentZoneController@revisions", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/api/v2/investment/admin/zones/{id}/{action}": { + "post": { + "operationId": "InvestmentZoneController_transition_17", + "summary": "InvestmentZoneController@transition", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "action", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": ["publish", "suspend"] + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "/api/v2/investment/zone-map": { + "get": { + "operationId": "InvestmentZoneGeometryController_map_18", + "summary": "InvestmentZoneGeometryController@map", + "parameters": [ + { + "name": "zone_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "bbox[]", + "in": "query", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "number" + } + }, + "description": "west,south,east,north; no antimeridian wrap." + } + ], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/api/v2/investment/zones": { + "get": { + "operationId": "InvestmentZoneController_index_19", + "summary": "InvestmentZoneController@index", + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "category", + "in": "query", + "schema": { + "type": "string", + "enum": ["zee", "zfi", "industrial_park"] + } + }, + { + "name": "physical_category", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "legal_regime", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "development_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "availability_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "activity", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "locality_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/api/v2/investment/zones/export.csv": { + "get": { + "operationId": "InvestmentZoneController_export_20", + "summary": "InvestmentZoneController@export", + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "category", + "in": "query", + "schema": { + "type": "string", + "enum": ["zee", "zfi", "industrial_park"] + } + }, + { + "name": "physical_category", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "legal_regime", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "development_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "availability_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "activity", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "locality_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "security": [], + "responses": { + "200": { + "description": "CSV filtered with the same server-side criteria as the public zone list.", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/api/v2/investment/zones/{id}": { + "get": { + "operationId": "InvestmentZoneController_show_21", + "summary": "InvestmentZoneController@show", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/management/investment/zones": { + "get": { + "operationId": "InvestmentZoneController_index_22", + "summary": "InvestmentZoneController@index", + "parameters": [ + { + "name": "q", + "in": "query", + "schema": { + "type": "string", + "maxLength": 120 + } + }, + { + "name": "category", + "in": "query", + "schema": { + "type": "string", + "enum": ["zee", "zfi", "industrial_park"] + } + }, + { + "name": "physical_category", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "legal_regime", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "development_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "availability_status", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "activity", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "province_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "district_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "admin_post_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "locality_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/management/investment/zones/{id}": { + "get": { + "operationId": "InvestmentZoneController_show_23", + "summary": "InvestmentZoneController@show", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/management/investment/zones/{zone}/attachments/{attachment}": { + "get": { + "operationId": "InvestmentZoneAttachmentController_downloadPrivate_24", + "summary": "InvestmentZoneAttachmentController@downloadPrivate", + "parameters": [ + { + "name": "zone", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "attachment", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "laravelSession": [] + } + ], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/zonas": { + "get": { + "operationId": "InvestmentZoneController_index_25", + "summary": "InvestmentZoneController@index", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/zonas/{id}": { + "get": { + "operationId": "InvestmentZoneController_show_26", + "summary": "InvestmentZoneController@show", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } + } + }, + "/zonas/{zone}/anexos/{attachment}": { + "get": { + "operationId": "InvestmentZoneAttachmentController_downloadPublic_27", + "summary": "InvestmentZoneAttachmentController@downloadPublic", + "parameters": [ + { + "name": "zone", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "attachment", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [], + "responses": { + "200": { + "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "403": { + "description": "Missing manager/MFA/designated role or self-review." + }, + "404": { + "description": "Not found or not publicly visible." + }, + "409": { + "description": "Stale version or duplicate relation." + }, + "419": { + "description": "Session/CSRF validation failed." + }, + "422": { + "description": "Invalid data, source, geometry, overlap or workflow." + }, + "429": { + "description": "Rate limit exceeded." + } + } } - } } - } }, - "/api/v2/investment/admin/zone-attachments/{id}/{action}": { - "post": { - "operationId": "InvestmentZoneAttachmentController_review_3", - "summary": "InvestmentZoneAttachmentController@review", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "components": { + "securitySchemes": { + "laravelSession": { + "type": "apiKey", + "in": "cookie", + "name": "laravel_session", + "description": "Actual session cookie name is configured by Laravel. Send X-XSRF-TOKEN from the session; no credentials embedded." } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "review", - "select-public", - "withdraw-public" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { + "schemas": { + "Version": { "type": "object", "properties": { - "version_no": { - "type": "integer", - "minimum": 1 - }, - "decision": { - "type": "string", - "enum": [ - "reviewed", - "rejected" - ] - }, - "rights_status": { - "type": "string", - "enum": [ - "pending", - "confirmed", - "restricted" - ] - }, - "rights_basis": { - "type": "string", - "nullable": true - }, - "personal_data_status": { - "type": "string", - "enum": [ - "none", - "present", - "unknown" - ] - } + "version_no": { + "type": "integer", + "minimum": 1 + } }, - "required": [ - "version_no" - ] - } - } - } - } - } - }, - "/api/v2/investment/admin/zone-geometries/{id}/{action}": { - "post": { - "operationId": "InvestmentZoneGeometryController_review_4", - "summary": "InvestmentZoneGeometryController@review", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "validate", - "revoke" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - } - } - }, - "/api/v2/investment/admin/zone-links/{id}/{action}": { - "post": { - "operationId": "InvestmentZoneLinkController_transition_5", - "summary": "InvestmentZoneLinkController@transition", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "validate", - "revoke" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - } - } - }, - "/api/v2/investment/admin/zone-locations/{id}/validate": { - "post": { - "operationId": "InvestmentZoneController_validateLocation_6", - "summary": "InvestmentZoneController@validateLocation", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Zone" - } - } - } - } - } - }, - "/api/v2/investment/admin/zone-relations/{id}/{action}": { - "post": { - "operationId": "InvestmentZoneRelationController_review_7", - "summary": "InvestmentZoneRelationController@review", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "validate", - "revoke" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones": { - "get": { - "operationId": "InvestmentZoneController_index_8", - "summary": "InvestmentZoneController@index", - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "category", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "zee", - "zfi", - "industrial_park" - ] - } - }, - { - "name": "physical_category", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "legal_regime", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "development_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "availability_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "activity", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "locality_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - }, - "post": { - "operationId": "InvestmentZoneController_save_9", - "summary": "InvestmentZoneController@save", - "parameters": [], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Zone" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}": { - "get": { - "operationId": "InvestmentZoneController_show_10", - "summary": "InvestmentZoneController@show", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - }, - "put": { - "operationId": "InvestmentZoneController_save_11", - "summary": "InvestmentZoneController@save", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Zone" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/attachments": { - "post": { - "operationId": "InvestmentZoneAttachmentController_store_12", - "summary": "InvestmentZoneAttachmentController@store", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { + "required": ["version_no"] + }, + "Zone": { "type": "object", "properties": { - "file": { - "type": "string", - "format": "binary" - }, - "kind": { - "type": "string", - "enum": [ - "legal_instrument", - "map", - "photo", - "study", - "other" - ] - }, - "caption": { - "type": "string", - "maxLength": 255 - }, - "source_reference": { - "type": "string", - "minLength": 5, - "maxLength": 2000 - }, - "source_date": { - "type": "string", - "format": "date", - "nullable": true - }, - "rights_status": { - "type": "string", - "enum": [ - "pending", - "confirmed", - "restricted" - ] - }, - "rights_basis": { - "type": "string", - "nullable": true - } + "version_no": { + "type": "integer", + "minimum": 1 + }, + "code": { + "type": "string", + "maxLength": 80 + }, + "category": { + "type": "string", + "enum": ["zee", "zfi", "industrial_park", "pending"] + }, + "physical_category": { + "type": "string", + "enum": [ + "economic_zone", + "industrial_park", + "agro_industrial_park", + "industrial_logistics_park", + "industrial_free_zone", + "integrated_tourism_area", + "other", + "unknown" + ] + }, + "legal_regime": { + "type": "string", + "enum": [ + "zee", + "zfi", + "general", + "pending", + "not_applicable" + ] + }, + "development_status": { + "type": "string", + "enum": [ + "announced", + "planned", + "in_development", + "partially_operational", + "operational", + "suspended", + "unknown" + ] + }, + "availability_status": { + "type": "string", + "enum": [ + "confirmed", + "unavailable", + "reported_unverified", + "not_informed" + ] + }, + "area_components": { + "type": "array", + "items": { + "type": "object" + } + }, + "source_evidence": { + "type": "array", + "items": { + "type": "object" + } + }, + "name": { + "type": "string", + "maxLength": 255 + }, + "name_en": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "description_en": { + "type": "string", + "nullable": true + }, + "province": { + "type": "string", + "nullable": true + }, + "district": { + "type": "string", + "nullable": true + }, + "operator_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_source_id": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": ["code", "category", "name"], + "description": "PUT requires version_no. Save withdraws publication and requires a new independent review. No official status inferred from category." + }, + "LegalSource": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTTPS reference only; no remote download." + }, + "diploma": { + "type": "string" + }, + "article": { + "type": "string" + }, + "competent_entity": { + "type": "string" + }, + "valid_from": { + "type": "string", + "format": "date" + }, + "valid_until": { + "type": "string", + "format": "date", + "nullable": true + } }, "required": [ - "file", - "kind", - "caption", - "source_reference", - "rights_status" + "title", + "url", + "diploma", + "article", + "competent_entity", + "valid_from" ] - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/geometries": { - "post": { - "operationId": "InvestmentZoneGeometryController_store_13", - "summary": "InvestmentZoneGeometryController@store", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Geometry" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/links": { - "post": { - "operationId": "InvestmentZoneLinkController_store_14", - "summary": "InvestmentZoneLinkController@store", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Link" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/relations": { - "post": { - "operationId": "InvestmentZoneRelationController_store_15", - "summary": "InvestmentZoneRelationController@store", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "201": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Relation" - } - } - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/revisions": { - "get": { - "operationId": "InvestmentZoneController_revisions_16", - "summary": "InvestmentZoneController@revisions", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/api/v2/investment/admin/zones/{id}/{action}": { - "post": { - "operationId": "InvestmentZoneController_transition_17", - "summary": "InvestmentZoneController@transition", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "action", - "in": "path", - "required": true, - "schema": { - "type": "string", - "enum": [ - "publish", - "suspend" - ] - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Version" - } - } - } - } - } - }, - "/api/v2/investment/zone-map": { - "get": { - "operationId": "InvestmentZoneGeometryController_map_18", - "summary": "InvestmentZoneGeometryController@map", - "parameters": [ - { - "name": "zone_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "bbox[]", - "in": "query", - "style": "form", - "explode": true, - "schema": { - "type": "array", - "minItems": 4, - "maxItems": 4, - "items": { - "type": "number" - } }, - "description": "west,south,east,north; no antimeridian wrap." - } - ], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/api/v2/investment/zones": { - "get": { - "operationId": "InvestmentZoneController_index_19", - "summary": "InvestmentZoneController@index", - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "category", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "zee", - "zfi", - "industrial_park" - ] - } - }, - { - "name": "physical_category", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "legal_regime", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "development_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "availability_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "activity", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "locality_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/api/v2/investment/zones/export.csv": { - "get": { - "operationId": "InvestmentZoneController_export_20", - "summary": "InvestmentZoneController@export", - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "category", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "zee", - "zfi", - "industrial_park" - ] - } - }, - { - "name": "physical_category", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "legal_regime", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "development_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "availability_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "activity", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "locality_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "security": [], - "responses": { - "200": { - "description": "CSV filtered with the same server-side criteria as the public zone list.", - "content": { - "text/csv": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/api/v2/investment/zones/{id}": { - "get": { - "operationId": "InvestmentZoneController_show_21", - "summary": "InvestmentZoneController@show", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/management/investment/zones": { - "get": { - "operationId": "InvestmentZoneController_index_22", - "summary": "InvestmentZoneController@index", - "parameters": [ - { - "name": "q", - "in": "query", - "schema": { - "type": "string", - "maxLength": 120 - } - }, - { - "name": "category", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "zee", - "zfi", - "industrial_park" - ] - } - }, - { - "name": "physical_category", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "legal_regime", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "development_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "availability_status", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "activity", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "province_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "district_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "admin_post_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "locality_id", - "in": "query", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "minimum": 1 - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/management/investment/zones/{id}": { - "get": { - "operationId": "InvestmentZoneController_show_23", - "summary": "InvestmentZoneController@show", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/management/investment/zones/{zone}/attachments/{attachment}": { - "get": { - "operationId": "InvestmentZoneAttachmentController_downloadPrivate_24", - "summary": "InvestmentZoneAttachmentController@downloadPrivate", - "parameters": [ - { - "name": "zone", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "attachment", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [ - { - "laravelSession": [] - } - ], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/zonas": { - "get": { - "operationId": "InvestmentZoneController_index_25", - "summary": "InvestmentZoneController@index", - "parameters": [], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/zonas/{id}": { - "get": { - "operationId": "InvestmentZoneController_show_26", - "summary": "InvestmentZoneController@show", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - }, - "/zonas/{zone}/anexos/{attachment}": { - "get": { - "operationId": "InvestmentZoneAttachmentController_downloadPublic_27", - "summary": "InvestmentZoneAttachmentController@downloadPublic", - "parameters": [ - { - "name": "zone", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "attachment", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "security": [], - "responses": { - "200": { - "description": "Success. JSON; paginated lists. Private management responses use Cache-Control: private, no-store.", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "403": { - "description": "Missing manager/MFA/designated role or self-review." - }, - "404": { - "description": "Not found or not publicly visible." - }, - "409": { - "description": "Stale version or duplicate relation." - }, - "419": { - "description": "Session/CSRF validation failed." - }, - "422": { - "description": "Invalid data, source, geometry, overlap or workflow." - }, - "429": { - "description": "Rate limit exceeded." - } - } - } - } - }, - "components": { - "securitySchemes": { - "laravelSession": { - "type": "apiKey", - "in": "cookie", - "name": "laravel_session", - "description": "Actual session cookie name is configured by Laravel. Send X-XSRF-TOKEN from the session; no credentials embedded." - } - }, - "schemas": { - "Version": { - "type": "object", - "properties": { - "version_no": { - "type": "integer", - "minimum": 1 - } - }, - "required": [ - "version_no" - ] - }, - "Zone": { - "type": "object", - "properties": { - "version_no": { - "type": "integer", - "minimum": 1 - }, - "code": { - "type": "string", - "maxLength": 80 - }, - "category": { - "type": "string", - "enum": [ - "zee", - "zfi", - "industrial_park", - "pending" - ] - }, - "physical_category": { - "type": "string", - "enum": [ - "economic_zone", - "industrial_park", - "agro_industrial_park", - "industrial_logistics_park", - "industrial_free_zone", - "integrated_tourism_area", - "other", - "unknown" - ] - }, - "legal_regime": { - "type": "string", - "enum": [ - "zee", - "zfi", - "general", - "pending", - "not_applicable" - ] - }, - "development_status": { - "type": "string", - "enum": [ - "announced", - "planned", - "in_development", - "partially_operational", - "operational", - "suspended", - "unknown" - ] - }, - "availability_status": { - "type": "string", - "enum": [ - "confirmed", - "unavailable", - "reported_unverified", - "not_informed" - ] - }, - "area_components": { - "type": "array", - "items": { - "type": "object" - } - }, - "source_evidence": { - "type": "array", - "items": { - "type": "object" - } - }, - "name": { - "type": "string", - "maxLength": 255 - }, - "name_en": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string", - "nullable": true - }, - "description_en": { - "type": "string", - "nullable": true - }, - "province": { - "type": "string", - "nullable": true - }, - "district": { - "type": "string", - "nullable": true - }, - "operator_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "legal_source_id": { - "type": "string", - "format": "uuid", - "nullable": true - } - }, - "required": [ - "code", - "category", - "name" - ], - "description": "PUT requires version_no. Save withdraws publication and requires a new independent review. No official status inferred from category." - }, - "LegalSource": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri", - "description": "HTTPS reference only; no remote download." - }, - "diploma": { - "type": "string" - }, - "article": { - "type": "string" - }, - "competent_entity": { - "type": "string" - }, - "valid_from": { - "type": "string", - "format": "date" - }, - "valid_until": { - "type": "string", - "format": "date", - "nullable": true - } - }, - "required": [ - "title", - "url", - "diploma", - "article", - "competent_entity", - "valid_from" - ] - }, - "Link": { - "type": "object", - "properties": { - "opportunity_id": { - "type": "integer", - "minimum": 1 - }, - "association_type": { - "type": "string", - "enum": [ - "official", - "influence", - "proximity", - "potential", - "historical" - ] - }, - "role": { - "type": "string", - "enum": [ - "primary", - "beneficiary", - "served", - "enabled", - "dependent", - "related", - "potential", - "historical" - ] - }, - "source_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "justification": { - "type": "string", - "minLength": 10, - "maxLength": 3000 - }, - "legal_source_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "valid_from": { - "type": "string", - "format": "date" - }, - "valid_until": { - "type": "string", - "format": "date", - "nullable": true - }, - "priority": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "is_primary": { - "type": "boolean" - } - }, - "required": [ - "opportunity_id", - "association_type", - "role", - "source_reference", - "justification", - "valid_from" - ], - "description": "Official association requires legal_source_id. Creates or reuses UUID mapping for existing opportunity; duplicate zone-item pair returns 409. Potential remains potential after review." - }, - "Relation": { - "type": "object", - "properties": { - "cluster_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "corridor_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "project_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "association_type": { - "type": "string", - "enum": [ - "official", - "influence", - "proximity", - "potential", - "historical" - ] - }, - "role": { - "type": "string", - "enum": [ - "primary", - "beneficiary", - "served", - "enabled", - "dependent", - "related", - "potential", - "historical" - ] - }, - "source_reference": { - "type": "string", - "minLength": 10, - "maxLength": 2000 - }, - "justification": { - "type": "string", - "minLength": 10, - "maxLength": 3000 - }, - "legal_source_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "valid_from": { - "type": "string", - "format": "date" - }, - "valid_until": { - "type": "string", - "format": "date", - "nullable": true - }, - "priority": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "is_primary": { - "type": "boolean" - } - }, - "required": [ - "association_type", - "role", - "source_reference", - "justification", - "valid_from" - ], - "description": "Exactly one of cluster_id, corridor_id, project_id. Official association requires legal_source_id. Unique zone-target pair; duplicate returns 409." - }, - "Geometry": { - "type": "object", - "properties": { - "geometry": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "Point", - "LineString", - "Polygon", - "MultiPolygon" - ] - }, - "coordinates": { - "type": "array", - "items": {} - } + "Link": { + "type": "object", + "properties": { + "opportunity_id": { + "type": "integer", + "minimum": 1 + }, + "association_type": { + "type": "string", + "enum": [ + "official", + "influence", + "proximity", + "potential", + "historical" + ] + }, + "role": { + "type": "string", + "enum": [ + "primary", + "beneficiary", + "served", + "enabled", + "dependent", + "related", + "potential", + "historical" + ] + }, + "source_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "justification": { + "type": "string", + "minLength": 10, + "maxLength": 3000 + }, + "legal_source_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "valid_from": { + "type": "string", + "format": "date" + }, + "valid_until": { + "type": "string", + "format": "date", + "nullable": true + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "is_primary": { + "type": "boolean" + } + }, + "required": [ + "opportunity_id", + "association_type", + "role", + "source_reference", + "justification", + "valid_from" + ], + "description": "Official association requires legal_source_id. Creates or reuses UUID mapping for existing opportunity; duplicate zone-item pair returns 409. Potential remains potential after review." }, - "required": [ - "type", - "coordinates" - ] - }, - "purpose": { - "type": "string", - "enum": [ - "official_perimeter", - "influence", - "site", - "alternative" - ] - }, - "legal_source_id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "source_reference": { - "type": "string" - }, - "validating_entity": { - "type": "string" - }, - "observed_at": { - "type": "string", - "format": "date" - } - }, - "required": [ - "geometry", - "purpose", - "source_reference", - "validating_entity", - "observed_at" - ], - "description": "GeoJSON geometry in EPSG:4326, 2D, valid, nonempty, max 200000 encoded bytes. Official perimeter requires Polygon/MultiPolygon and legal source. No status inferred from geometry." - } + "Relation": { + "type": "object", + "properties": { + "cluster_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "corridor_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "project_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "association_type": { + "type": "string", + "enum": [ + "official", + "influence", + "proximity", + "potential", + "historical" + ] + }, + "role": { + "type": "string", + "enum": [ + "primary", + "beneficiary", + "served", + "enabled", + "dependent", + "related", + "potential", + "historical" + ] + }, + "source_reference": { + "type": "string", + "minLength": 10, + "maxLength": 2000 + }, + "justification": { + "type": "string", + "minLength": 10, + "maxLength": 3000 + }, + "legal_source_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "valid_from": { + "type": "string", + "format": "date" + }, + "valid_until": { + "type": "string", + "format": "date", + "nullable": true + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "is_primary": { + "type": "boolean" + } + }, + "required": [ + "association_type", + "role", + "source_reference", + "justification", + "valid_from" + ], + "description": "Exactly one of cluster_id, corridor_id, project_id. Official association requires legal_source_id. Unique zone-target pair; duplicate returns 409." + }, + "Geometry": { + "type": "object", + "properties": { + "geometry": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "Point", + "LineString", + "Polygon", + "MultiPolygon" + ] + }, + "coordinates": { + "type": "array", + "items": {} + } + }, + "required": ["type", "coordinates"] + }, + "purpose": { + "type": "string", + "enum": [ + "official_perimeter", + "influence", + "site", + "alternative" + ] + }, + "legal_source_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "source_reference": { + "type": "string" + }, + "validating_entity": { + "type": "string" + }, + "observed_at": { + "type": "string", + "format": "date" + } + }, + "required": [ + "geometry", + "purpose", + "source_reference", + "validating_entity", + "observed_at" + ], + "description": "GeoJSON geometry in EPSG:4326, 2D, valid, nonempty, max 200000 encoded bytes. Official perimeter requires Polygon/MultiPolygon and legal source. No status inferred from geometry." + } + } } - } } diff --git a/docs/investmoz/zones/plan-and-traceability.md b/docs/investmoz/zones/plan-and-traceability.md index 55c8d257..19a48325 100644 --- a/docs/investmoz/zones/plan-and-traceability.md +++ b/docs/investmoz/zones/plan-and-traceability.md @@ -4,25 +4,25 @@ Tudo nesta página é proposto, excepto os artefactos de Fase 0. Caminhos e endp Actualização: a matriz original abaixo conserva o plano. O [estado de implementação](implementation-status.md) e a tabela seguinte distinguem o código agora existente dos requisitos ainda pendentes. A autorização posterior permite progressão automática; os gates não exigem nova confirmação de fase. -| Requisito coberto parcialmente | Migration / modelo | Serviço / API / ecrã existentes | Evidência automatizada | Responsabilidade | -| --- | --- | --- | --- | --- | -| RF-ZON-001/005/015 | `100000_investment_zone_foundation`, InvestmentZone | InvestmentZones; InvestmentZoneController; `/api/v2/investment/admin/zones`; `investment/zone.tsx` | InvestmentZoneTest: MFA, versão, revisão independente, snapshots encriptados e imutáveis | Implementação técnica executada; responsável institucional por designar | -| RF-ZON-003 | mesma migration; InvestmentLegalSource | InvestmentLegalSourceController; API legal-sources; formulário de fontes | Criação/revisão/revogação e ocultação imediata dos resultados dependentes | Jurídico por designar; nenhum benefício carregado | -| RF-ZON-002 | `110000_zone_geometry_review`; geo_geometries existente | InvestmentZoneGeometryController; zone-map; `zone-map.tsx` e `zone-relations.tsx` | Geometrias inválidas, revisão independente, fonte retirada, mapa sem divulgação indevida | GIS por designar | -| RF-ZON-006/012; RF-CLU/COR | identidade e M:N da migration 100000 | InvestmentIdentity/InvestmentCatalogue/InvestmentZones; v1 filtros opcionais e v2 zonas; catálogo e carteira | Oportunidade única em duas zonas; âncora em três zonas/parque; duplicados recusados | Dados/Produto por designar | -| RF-ZON-008 | `120000_investment_submission_zones` | InvestmentSubmissionController/InvestmentWorkspace; API v1 preservada; `investment/submission.tsx` | Rascunho deduplicado, protocolo idempotente, origem no atendimento, zona revogada recusada | Atendimento existente; homologação CRM externa pendente | -| Migração Fase 6 | correspondências tipadas | `investment:reconcile-identities` | Dry-run sem mutações; aplicação/repetição preserva UUID e dados legados | Execução local reconciliada | +| Requisito coberto parcialmente | Migration / modelo | Serviço / API / ecrã existentes | Evidência automatizada | Responsabilidade | +| ------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | +| RF-ZON-001/005/015 | `100000_investment_zone_foundation`, InvestmentZone | InvestmentZones; InvestmentZoneController; `/api/v2/investment/admin/zones`; `investment/zone.tsx` | InvestmentZoneTest: MFA, versão, revisão independente, snapshots encriptados e imutáveis | Implementação técnica executada; responsável institucional por designar | +| RF-ZON-003 | mesma migration; InvestmentLegalSource | InvestmentLegalSourceController; API legal-sources; formulário de fontes | Criação/revisão/revogação e ocultação imediata dos resultados dependentes | Jurídico por designar; nenhum benefício carregado | +| RF-ZON-002 | `110000_zone_geometry_review`; geo_geometries existente | InvestmentZoneGeometryController; zone-map; `zone-map.tsx` e `zone-relations.tsx` | Geometrias inválidas, revisão independente, fonte retirada, mapa sem divulgação indevida | GIS por designar | +| RF-ZON-006/012; RF-CLU/COR | identidade e M:N da migration 100000 | InvestmentIdentity/InvestmentCatalogue/InvestmentZones; v1 filtros opcionais e v2 zonas; catálogo e carteira | Oportunidade única em duas zonas; âncora em três zonas/parque; duplicados recusados | Dados/Produto por designar | +| RF-ZON-008 | `120000_investment_submission_zones` | InvestmentSubmissionController/InvestmentWorkspace; API v1 preservada; `investment/submission.tsx` | Rascunho deduplicado, protocolo idempotente, origem no atendimento, zona revogada recusada | Atendimento existente; homologação CRM externa pendente | +| Migração Fase 6 | correspondências tipadas | `investment:reconcile-identities` | Dry-run sem mutações; aplicação/repetição preserva UUID e dados legados | Execução local reconciliada | -| Fase | Ficheiros / alterações previstas | Critério de saída | -| --- | --- | --- | -| 0 | `tools/zones-baseline.php`; `docs/investmoz/zones/*` | Inventário reproduzível, testes, diagnóstico, aprovação do piloto | -| 1 | Novas migrations aditivas `investment_identity`, `investment_zones`, `legal_evidence`, `typed_relations`, `geometry_provenance`; `docs/investmoz/zones/{erd,data-dictionary,legal-matrix,migration-plan}.md` | ER, FKs, unicidade, vigência, checks GIS, dry-run e rollback testados; aprovação Arquitectura/Dados/GIS/Jurídico | -| 2 | `app/Models/{InvestmentItem,InvestmentZone,LegalEvidence}.php`; services de revisão/publicação; policies; controllers/resources/requests; `routes/investment-zones.php`; OpenAPI | RBAC, MFA privilegiado, concorrência, histórico, autorização e contratos aprovados | -| 3 | Evoluir `InvestmentCatalogue`; repositório espacial; índices de pesquisa; telemetria agregada | Quatro dimensões, deduplicação por UUID, relações explicáveis, relevância e desempenho medidos | -| 4 | `resources/js/pages/investment/{catalogue,detail}.tsx`; páginas zonas; selector de dimensão, camadas/mapa e traduções | PT/EN, filtros URL, teclado, contraste, móvel, falhas parciais e E2E | -| 5 | `InvestmentSubmissionController`, intake, adaptador SuiteCRM, jobs/outbox, reconciliação | Interesse único com todas as origens, consentimento, retry e falha externa testados | -| 6 | Comandos dry-run/apply/reconcile; relatórios privados; runbook; fixtures sintéticas | Sem perda de IDs/URLs, restauro ensaiado, segurança/carga/UAT e formação | -| 7 | Deploy, observabilidade e plano operacional | Aprovação formal de produção e suporte | +| Fase | Ficheiros / alterações previstas | Critério de saída | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | +| 0 | `tools/zones-baseline.php`; `docs/investmoz/zones/*` | Inventário reproduzível, testes, diagnóstico, aprovação do piloto | +| 1 | Novas migrations aditivas `investment_identity`, `investment_zones`, `legal_evidence`, `typed_relations`, `geometry_provenance`; `docs/investmoz/zones/{erd,data-dictionary,legal-matrix,migration-plan}.md` | ER, FKs, unicidade, vigência, checks GIS, dry-run e rollback testados; aprovação Arquitectura/Dados/GIS/Jurídico | +| 2 | `app/Models/{InvestmentItem,InvestmentZone,LegalEvidence}.php`; services de revisão/publicação; policies; controllers/resources/requests; `routes/investment-zones.php`; OpenAPI | RBAC, MFA privilegiado, concorrência, histórico, autorização e contratos aprovados | +| 3 | Evoluir `InvestmentCatalogue`; repositório espacial; índices de pesquisa; telemetria agregada | Quatro dimensões, deduplicação por UUID, relações explicáveis, relevância e desempenho medidos | +| 4 | `resources/js/pages/investment/{catalogue,detail}.tsx`; páginas zonas; selector de dimensão, camadas/mapa e traduções | PT/EN, filtros URL, teclado, contraste, móvel, falhas parciais e E2E | +| 5 | `InvestmentSubmissionController`, intake, adaptador SuiteCRM, jobs/outbox, reconciliação | Interesse único com todas as origens, consentimento, retry e falha externa testados | +| 6 | Comandos dry-run/apply/reconcile; relatórios privados; runbook; fixtures sintéticas | Sem perda de IDs/URLs, restauro ensaiado, segurança/carga/UAT e formação | +| 7 | Deploy, observabilidade e plano operacional | Aprovação formal de produção e suporte | Modelo conceptual de relações, ainda sujeito ao ER da Fase 1: investimento → correspondências de oportunidade/projecto; investimento ↔ cluster/corredor/zona/parque/cadeia/infra-estrutura/território; zona → subzona/lote/instalação; zona ↔ operador/empresa/actividade; evidência jurídica → versões e validações; dimensão/relação/geometria → revisões e auditoria. PK/FK e cardinalidades exactas serão especificadas antes das migrations. @@ -30,26 +30,26 @@ Modelo conceptual de relações, ainda sujeito ao ER da Fase 1: investimento → `Z1` = migration futura de zonas; `L1` = evidência jurídica; `R1` = relações tipadas; `G1` = proveniência GIS; `I1` = identidade canónica. `API zonas` e `API catálogo v2` são contratos a desenhar; API v1 é preservada. Cada teste abaixo é planeado, não executado. -| Requisito | Entidade / migration | Modelo e serviço | API / ecrã | Teste de aceitação | Fase / responsável | -| --- | --- | --- | --- | --- | --- | -| RF-ZON-001 | Zona, revisão / Z1 | InvestmentZone, revisão | API zonas / cadastro | código único, concorrência, histórico | 1–2 / Dados | -| RF-ZON-002 | Geometria, lote / G1 | geometria e validação | mapa / perímetro | SRID, vazio, validade, conflito, acesso | 1–4 / GIS | -| RF-ZON-003 | Regra legal / L1 | regra e vigência | regimes / benefícios | expiração, contradição, não cumulação | 1–2 / Jurídico | -| RF-ZON-004 | Actividade/restrição / L1+R1 | elegibilidade assistida | actividades / condições | fundamento e excepção válidos | 1–2 / Jurídico | -| RF-ZON-005 | Ficha bilingue / Z1 | publicação | zona / detalhe | duas validações, fonte e tradução | 2–4 / Editorial | -| RF-ZON-006 | Item e dimensões / I1+R1 | pesquisa | catálogo / comparação | resultados únicos, filtros e acesso | 3–4 / Produto | -| RF-ZON-007 | Lote/instalação / Z1+G1 | ocupação | administração | reserva concorrente, contrato privado | 2–4 / Operação | -| RF-ZON-008 | Manifestação/origens / R1 | intake | submissions / formulário | protocolo único, consentimento | 5 / CRM | -| RF-ZON-009 | Processo e parecer / L1 | workflow assistido | processo / acompanhamento | sem acto oficial automático | 2–5 / Jurídico | -| RF-ZON-010 | Título/obrigação / L1 | validade e alertas | títulos / painel | vencimento retira informação vigente | 2 / Jurídico | -| RF-ZON-011 | Movimento/documento / Z1+L1 | operação assistida | gestão privada | não autoriza saída sem referência | 2–5 / Operação aduaneira | -| RF-ZON-012 | Relações / R1 | associações | dimensões / relações | M:N, fonte, período, sem duplicação | 1–4 / Dados | -| RF-ZON-013 | Infra-estrutura / R1 | capacidades | zona / infra-estruturas | capacidade, fonte, incidente | 2–4 / Operação | -| RF-ZON-014 | Indicador / Z1 | consolidação | indicadores / painel | separar realizado, autorizado, estimado | 2–4 / Dados | -| RF-ZON-015 | Papéis/revisões / L1 | policies e auditoria | administração | proibir auto-aprovação, isolamento | 1–6 / Segurança | -| RF-ZON-016 | Outbox/adaptador | integração | API versionada / operação | idempotência, retry, acordo formal | 5 / Integrações | -| RF-CLU-001* | Cluster/subcluster/cadeia / R1 | descoberta | catálogo / cluster | âncoras, elos, lacunas, fonte da relação | 1–4 / Produto e Dados | -| RF-COR-001* | Corredor/nó/influência / R1+G1 | descoberta espacial | catálogo / corredor | distinguir oficial, influência, proximidade | 1–4 / Produto e GIS | +| Requisito | Entidade / migration | Modelo e serviço | API / ecrã | Teste de aceitação | Fase / responsável | +| ----------- | ------------------------------ | ----------------------- | ------------------------- | ------------------------------------------- | ------------------------ | +| RF-ZON-001 | Zona, revisão / Z1 | InvestmentZone, revisão | API zonas / cadastro | código único, concorrência, histórico | 1–2 / Dados | +| RF-ZON-002 | Geometria, lote / G1 | geometria e validação | mapa / perímetro | SRID, vazio, validade, conflito, acesso | 1–4 / GIS | +| RF-ZON-003 | Regra legal / L1 | regra e vigência | regimes / benefícios | expiração, contradição, não cumulação | 1–2 / Jurídico | +| RF-ZON-004 | Actividade/restrição / L1+R1 | elegibilidade assistida | actividades / condições | fundamento e excepção válidos | 1–2 / Jurídico | +| RF-ZON-005 | Ficha bilingue / Z1 | publicação | zona / detalhe | duas validações, fonte e tradução | 2–4 / Editorial | +| RF-ZON-006 | Item e dimensões / I1+R1 | pesquisa | catálogo / comparação | resultados únicos, filtros e acesso | 3–4 / Produto | +| RF-ZON-007 | Lote/instalação / Z1+G1 | ocupação | administração | reserva concorrente, contrato privado | 2–4 / Operação | +| RF-ZON-008 | Manifestação/origens / R1 | intake | submissions / formulário | protocolo único, consentimento | 5 / CRM | +| RF-ZON-009 | Processo e parecer / L1 | workflow assistido | processo / acompanhamento | sem acto oficial automático | 2–5 / Jurídico | +| RF-ZON-010 | Título/obrigação / L1 | validade e alertas | títulos / painel | vencimento retira informação vigente | 2 / Jurídico | +| RF-ZON-011 | Movimento/documento / Z1+L1 | operação assistida | gestão privada | não autoriza saída sem referência | 2–5 / Operação aduaneira | +| RF-ZON-012 | Relações / R1 | associações | dimensões / relações | M:N, fonte, período, sem duplicação | 1–4 / Dados | +| RF-ZON-013 | Infra-estrutura / R1 | capacidades | zona / infra-estruturas | capacidade, fonte, incidente | 2–4 / Operação | +| RF-ZON-014 | Indicador / Z1 | consolidação | indicadores / painel | separar realizado, autorizado, estimado | 2–4 / Dados | +| RF-ZON-015 | Papéis/revisões / L1 | policies e auditoria | administração | proibir auto-aprovação, isolamento | 1–6 / Segurança | +| RF-ZON-016 | Outbox/adaptador | integração | API versionada / operação | idempotência, retry, acordo formal | 5 / Integrações | +| RF-CLU-001* | Cluster/subcluster/cadeia / R1 | descoberta | catálogo / cluster | âncoras, elos, lacunas, fonte da relação | 1–4 / Produto e Dados | +| RF-COR-001* | Corredor/nó/influência / R1+G1 | descoberta espacial | catálogo / corredor | distinguir oficial, influência, proximidade | 1–4 / Produto e GIS | *IDs locais atribuídos aos requisitos textuais de Cluster/Corredor do documento, que não lhes atribui numeração explícita. diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 05fbd31b..62b1a4e5 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -26,6 +26,9 @@ void createInertiaApp({ case name === 'management-access': case name.startsWith('portal/'): case name.startsWith('investment/'): + case name.startsWith('portfolio/'): + case name.startsWith('land-assistance/'): + case name.startsWith('desk/'): return PortalNavigation; case name.startsWith('auth/'): return [PortalNavigation, AuthLayout]; diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 3b787cde..d50e92cf 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,7 +1,7 @@ import { Link, usePage } from '@inertiajs/react'; -import { BookOpen, FolderGit2, LayoutGrid } from 'lucide-react'; +import { LayoutGrid } from 'lucide-react'; +import { portalNavigation } from '@/lib/portal-navigation'; import AppLogo from '@/components/app-logo'; -import { NavFooter } from '@/components/nav-footer'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; import { @@ -14,64 +14,28 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { dashboard } from '@/routes'; -import type { NavItem, Auth } from '@/types'; - -const mainNavItems: NavItem[] = [ - { title: 'Painel do investidor', href: '/investidor', icon: LayoutGrid }, - { title: 'Corredores e clusters', href: '/carteira', icon: LayoutGrid }, - { - title: 'Dashboard', - href: dashboard(), - icon: LayoutGrid, - }, -]; - -const footerNavItems: NavItem[] = [ - { - title: 'Repository', - href: 'https://github.com/laravel/react-starter-kit', - icon: FolderGit2, - }, - { - title: 'Documentation', - href: 'https://laravel.com/docs/starter-kits#react', - icon: BookOpen, - }, -]; +import type { Auth } from '@/types'; export function AppSidebar() { - const { auth } = usePage<{ auth: Auth }>().props; - const items = auth.canManagePortal - ? [ - ...mainNavItems, - { - title: 'Pedidos de interesse', - href: '/management/interests', - icon: LayoutGrid, - }, - { - title: 'Sectores', - href: '/management/sectors', - icon: LayoutGrid, - }, - { - title: 'Oportunidades', - href: '/management/opportunities', - icon: LayoutGrid, - }, - { title: 'CRM', href: '/management/crm/leads', icon: LayoutGrid }, - { - title: 'Projectos', - href: '/management/projects', - icon: LayoutGrid, - }, - { - title: 'Clusters', - href: '/management/clusters', - icon: LayoutGrid, - }, - ] - : mainNavItems; + const { auth, existingBusinessEnabled, educationEnabled, aiEnabled } = + usePage<{ + auth: Auth; + existingBusinessEnabled?: boolean; + educationEnabled?: boolean; + aiEnabled?: boolean; + }>().props; + const groups = portalNavigation({ + authenticated: !!auth.user, + manager: auth.canManagePortal, + businesses: existingBusinessEnabled, + education: educationEnabled, + ai: aiEnabled, + }); + const items = groups.map((group) => ({ + title: group.label, + href: group.children?.find((item) => item.href)?.href ?? '/', + icon: LayoutGrid, + })); return ( @@ -91,7 +55,6 @@ export function AppSidebar() { - diff --git a/resources/js/components/desk-internal-operations.tsx b/resources/js/components/desk-internal-operations.tsx new file mode 100644 index 00000000..20aceb75 --- /dev/null +++ b/resources/js/components/desk-internal-operations.tsx @@ -0,0 +1,539 @@ +import { useForm } from '@inertiajs/react'; +import { card, input, button } from '@/pages/investment/shared'; + +export type DeskDocument = { + id: string; + document_id: string; + file_name: string; + status: string; + malware_scan_status: string; + category: string; + source_reference: string; +}; +export function DocumentReview({ + caseId, + version, + documents, + requestId, +}: { + caseId: string; + version: number; + documents: DeskDocument[]; + requestId?: string; +}) { + const form = useForm({ + version, + request_id: requestId ?? null, + document: documents[0]?.id ?? '', + outcome: 'internal_verified', + what_checked: '', + evidence: '', + supports: [] as string[], + }); + return ( +
{ + e.preventDefault(); + form.transform((d) => ({ ...d, version })); + form.post( + `/balcao-virtual/dossiers/${caseId}/documentos/${form.data.document}/conferencia`, + ); + }} + > +

+ Conferência documental interna +

+

+ O registo identifica a pessoa, o ficheiro e hash, o alcance e a + evidência. Não equivale a aprovação, registo ou licença de uma + instituição. +

+ + + +
+ Pressupostos abrangidos por esta conferência +

+ Seleccione apenas os âmbitos efectivamente verificados. + Associar um ficheiro não valida automaticamente um + pressuposto. +

+ {Object.entries({ + market_evidence: 'Mercado', + location_evidence: 'Localização', + resource_evidence: 'Recursos', + funding_evidence: 'Financiamento', + }).map(([key, label]) => ( + + ))} +
+