diff --git a/app/Http/Controllers/Api/V1/SimulationTaxParameterController.php b/app/Http/Controllers/Api/V1/SimulationTaxParameterController.php index 4553446a..e769ae57 100644 --- a/app/Http/Controllers/Api/V1/SimulationTaxParameterController.php +++ b/app/Http/Controllers/Api/V1/SimulationTaxParameterController.php @@ -19,13 +19,15 @@ class SimulationTaxParameterController extends Controller $input['period_start'], $input['period_end'], $input['context'] ?? [], + $input['component'] === 'IVA', ); return response()->json([ 'component' => $input['component'], 'period' => ['start' => $input['period_start'], 'end' => $input['period_end']], 'assumptions' => $input['context'] ?? [], - 'parameters' => $data, + 'parameters' => $input['component'] === 'IVA' ? $data['parameters'] : $data, + 'diagnostics' => $input['component'] === 'IVA' ? $data['diagnostics'] : [], ])->header('Cache-Control', 'no-store'); } } diff --git a/app/Http/Requests/ResolveSimulationTaxParametersRequest.php b/app/Http/Requests/ResolveSimulationTaxParametersRequest.php index b7622aad..b6e0097d 100644 --- a/app/Http/Requests/ResolveSimulationTaxParametersRequest.php +++ b/app/Http/Requests/ResolveSimulationTaxParametersRequest.php @@ -21,7 +21,7 @@ class ResolveSimulationTaxParametersRequest extends FormRequest 'component' => ['required', 'in:IRPC,IVA,ISPC,IRPS,INSS,RETENCOES,DIREITOS_ADUANEIROS,ICE'], 'period_start' => ['required', 'date_format:Y-m-d'], 'period_end' => ['required', 'date_format:Y-m-d', 'after_or_equal:period_start'], - 'context' => ['sometimes', 'array:fiscal_periodicity,fiscal_operation,fiscal_residency_assessment,fiscal_residency_country,fiscal_income_category,fiscal_establishments,fiscal_documentation,fiscal_customs_code,fiscal_vat_refund_state'], + 'context' => ['sometimes', 'array:currency,fiscal_periodicity,fiscal_operation,fiscal_residency_assessment,fiscal_residency_country,fiscal_income_category,fiscal_establishments,fiscal_documentation,fiscal_customs_code,fiscal_vat_refund_state'], 'context.*' => ['string', 'max:160'], ]; } diff --git a/app/Http/Requests/StoreSimulationFormDraftRequest.php b/app/Http/Requests/StoreSimulationFormDraftRequest.php index 74cf0d90..d03064b0 100644 --- a/app/Http/Requests/StoreSimulationFormDraftRequest.php +++ b/app/Http/Requests/StoreSimulationFormDraftRequest.php @@ -50,7 +50,7 @@ class StoreSimulationFormDraftRequest extends FormRequest 'environmental_category', 'community_benefits', 'tax_calculation', 'fiscal_scenario_mode', 'base', 'rate', 'reduction', 'sales_taxable', 'sales_exempt', 'sales_zero_rate', 'exports', 'deductible_costs', 'non_deductible_costs', 'carried_losses', 'payroll', 'outbound_payments', 'imports', 'claimed_benefits', 'reference_date', - 'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment', + 'fiscal_currency', 'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment', 'fiscal_operation', 'fiscal_activity', 'fiscal_establishments', 'fiscal_documentation', 'fiscal_customs_code', 'fiscal_vat_refund_state', 'offer_or_demand', 'opportunity_type', 'opportunity_type_other', 'opportunity_description', @@ -279,7 +279,7 @@ class StoreSimulationFormDraftRequest extends FormRequest 'currency', 'funds_origin_countries', 'environmental_category', 'tax_calculation', 'fiscal_scenario_mode', 'offer_or_demand', 'opportunity_type', 'confidentiality', 'publish_requested', 'terms_accepted', 'truth_declaration', 'reuse_simulation', 'contact_authorisation', 'publication_authorisation', 'public_fields_confirmation', 'validation_acknowledged', - 'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment', 'fiscal_operation', + 'fiscal_currency', 'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment', 'fiscal_operation', 'fiscal_documentation', 'fiscal_vat_refund_state'], 'personal_snapshot.selection_labels' => ['person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage', 'legal_form', 'preferred_channel'], ]; diff --git a/app/Services/SimulationIvaDiagnostics.php b/app/Services/SimulationIvaDiagnostics.php new file mode 100644 index 00000000..096bcc4d --- /dev/null +++ b/app/Services/SimulationIvaDiagnostics.php @@ -0,0 +1,54 @@ + $version['valid_until'])) { + $add('period_unavailable', 'fiscal_period', 'Período', "$start a $end", $version['valid_from'].' a '.($version['valid_until'] ?? 'sem data final indicada'), 'Esta versão da regra não abrange todo o período seleccionado. Não altere a data real da operação para obter um resultado.'); + } + foreach ($parameter['conditions'] ?? [] as $key => $expected) { + $received = $context[$key] ?? null; + if ($received === $expected) { + continue; + } + if ($key === 'currency') { + $category = $received === null || $received === '' ? 'missing_field' : (preg_match('/^[A-Z]{3}$/D', $received) ? 'out_of_scope' : 'invalid_value'); + $add($category, 'fiscal_currency', 'Moeda', $received, $expected, $category === 'missing_field' ? 'Indique a moeda da operação.' : 'Esta versão aceita apenas '.$expected.'. Reveja a moeda ou utilize um cálculo adequado à operação; a resposta não foi alterada.'); + } elseif ($key === 'operation' && ! array_key_exists('operation', $context) && $expected === 'internal_general_net' && in_array($context['fiscal_operation'] ?? null, ['import', 'export', 'non_resident', 'refund'], true)) { + $add('out_of_scope', 'fiscal_operation', 'Operação', $context['fiscal_operation'], 'Operação interna à taxa geral, sobre base líquida já apurada', 'A operação declarada está excluída desta versão. Utilize um cálculo adequado à operação; não altere a resposta para obter um resultado.'); + } elseif ($key === 'operation' && ! array_key_exists('operation', $context)) { + $add('system_configuration', 'fiscal_operation', 'Operação', $context['fiscal_operation'] ?? null, $expected === 'internal_general_net' ? 'Operação interna à taxa geral, sobre base líquida já apurada' : $expected, 'Limitação de integração do sistema: esta consulta não comunica o enquadramento específico exigido pela versão. A resposta genérica não permite inferi-lo. Utilize o formulário IVA — taxa geral, com declaração explícita do âmbito; não altere respostas para forçar compatibilidade.'); + } elseif ($key === 'operation') { + $add($received === null || $received === '' ? 'missing_field' : 'out_of_scope', 'fiscal_operation', 'Operação', $received, $expected === 'internal_general_net' ? 'Operação interna à taxa geral, sobre base líquida já apurada' : $expected, 'A operação não está abrangida por esta versão; utilize um cálculo adequado à operação declarada.'); + } else { + $add('system_configuration', null, 'Configuração da regra', null, 'Condições publicadas ligadas a campos deste formulário', 'O sistema não consegue relacionar todas as condições desta versão com o formulário. Não é uma falha das suas respostas.'); + } + } + if (! SimulationFiscalSourceAcceptance::eligible($version['source'] ?? [], $version['source_sha256'] ?? null) + || ($version['source_version_no'] ?? null) !== ($version['source']['version_no'] ?? null) + || ($version['rule_definition']['language'] ?? null) !== 'investmoz.tax.v1') { + $add('system_configuration', null, 'Regra', null, 'Versão publicada com fonte e configuração coerentes', 'A configuração ou a fonte da regra não permite resolver este parâmetro. Não altere as respostas; é necessária correcção do sistema.'); + } + if ($issues !== []) { + $source = array_intersect_key($version['source'] ?? [], array_flip(['title', 'diploma', 'article', 'url'])); + $diagnostics[] = ['label' => 'IVA — taxa geral', 'rule_version' => $version['rule_version'], 'validity' => ['start' => $version['valid_from'], 'end' => $version['valid_until']], 'source' => $source, 'issues' => $issues]; + } + } + + return $diagnostics; + } +} diff --git a/app/Services/SimulationTaxParameterResolver.php b/app/Services/SimulationTaxParameterResolver.php index e669c5f5..7fcaf329 100644 --- a/app/Services/SimulationTaxParameterResolver.php +++ b/app/Services/SimulationTaxParameterResolver.php @@ -53,9 +53,9 @@ final class SimulationTaxParameterResolver * never executes a tax formula and requires each parameter to declare context. * * @param array $context - * @return list> + * @return list>|array{parameters:list>,diagnostics:list>} */ - public function resolvePublishedForPeriod(string $component, string $periodStart, string $periodEnd, array $context): array + public function resolvePublishedForPeriod(string $component, string $periodStart, string $periodEnd, array $context, bool $diagnostics = false): array { $start = $this->date($periodStart); $end = $this->date($periodEnd); @@ -67,14 +67,14 @@ final class SimulationTaxParameterResolver ->join('investment_legal_sources as source', 'source.id', '=', 'rule.legal_source_id') ->where('rule.component', $component) ->where('rule.status', 'published') - ->whereDate('rule.valid_from', '<=', $periodEnd) - ->where(fn ($query) => $query->whereNull('rule.valid_until')->orWhereDate('rule.valid_until', '>=', $periodStart)) + ->when(! $diagnostics, fn ($query) => $query->whereDate('rule.valid_from', '<=', $periodEnd) + ->where(fn ($query) => $query->whereNull('rule.valid_until')->orWhereDate('rule.valid_until', '>=', $periodStart))) ->where(fn ($query) => $query->where('source.validation_status', 'validated')->orWhere(fn ($fiscal) => $fiscal->where('source.validation_status', 'pending')->where('source.fiscal_acceptance->scope', 'fiscal_parameters') ->where('source.fiscal_acceptance->basis', 'Tempo-official-sources-20261008') ->whereRaw("source.fiscal_acceptance->>'sha256'=rule.source_sha256"))) ->whereColumn('source.version_no', 'rule.source_version_no') - ->whereDate('source.valid_from', '<=', $periodStart) - ->where(fn ($query) => $query->whereNull('source.valid_until')->orWhereDate('source.valid_until', '>=', $periodEnd)) + ->when(! $diagnostics, fn ($query) => $query->whereDate('source.valid_from', '<=', $periodStart) + ->where(fn ($query) => $query->whereNull('source.valid_until')->orWhereDate('source.valid_until', '>=', $periodEnd))) ->select([ 'rule.id', 'rule.rule_code', 'rule.rule_version', 'rule.component', 'rule.scope_key', 'rule.valid_from', 'rule.valid_until', 'rule.parameters', 'rule.rule_definition', @@ -121,7 +121,11 @@ final class SimulationTaxParameterResolver ]; })->all()); - return $this->resolveAvailableParameters($versions, $component, $periodStart, $periodEnd, $context); + $parameters = $this->resolveAvailableParameters($versions, $component, $periodStart, $periodEnd, $context); + + $resolvedIva = array_filter($parameters, fn (array $parameter): bool => $parameter['parameter_code'] === 'IVA_GENERAL_RATE' && $parameter['status'] === 'resolved'); + + return $diagnostics ? ['parameters' => $parameters, 'diagnostics' => $resolvedIva !== [] ? [] : (new SimulationIvaDiagnostics)->explain($versions, $periodStart, $periodEnd, $context)] : $parameters; } /** diff --git a/docs/investmoz/simulations/integrated-review-continuation-20261020-receipt.json b/docs/investmoz/simulations/integrated-review-continuation-20261020-receipt.json index ea8583c9..fd6d8102 100644 --- a/docs/investmoz/simulations/integrated-review-continuation-20261020-receipt.json +++ b/docs/investmoz/simulations/integrated-review-continuation-20261020-receipt.json @@ -1179,5 +1179,57 @@ "calculations_exporters_repeated": false }, "remaining": [] + }, + "iva_query_diagnostic_qa_20261021": { + "recorded_utc": "2026-10-21T11:35:00.737064+00:00", + "scope": "QA only, parameter-query diagnosis; no calculation/export retest, no destination writes", + "reported_surface": "Componentes fiscais e contributivas, IVA_GENERAL_RATE, annual 2026-01-01 to2026-12-31, indeterminate_context", + "confirmed_origin": "Implementation integration gap: query sent fiscal_operation and no currency/operation, whereas the published IVA parameter requires operation=internal_general_net and currency=MZN. Resolver collapsed every mismatch to indeterminate_context and UI blamed responses without individual evidence.", + "original_answers_known": "Only the period/component/message were provided. No original currency or operation is asserted; USD/domestic are explicit directed QA cases, not inferred original answers.", + "diagnostic_categories": [ + "missing_field", + "invalid_value", + "out_of_scope", + "period_unavailable", + "system_configuration" + ], + "changes": [ + "Server projects each failed criterion, field, received value, expected criterion, source and exact rule version; no expression projection", + "Explicit fiscal_currency answer for IVA query, persisted and recovered; no copy/conversion from budget or IVA-base currency", + "Generic domestic is never silently equated to internal_general_net; integration limitation is explained and links to explicit IVA form", + "Inline notes plus summary links, human IVA — taxa geral label and Ver fundamento disclosure", + "Period diagnostic can inspect published candidates outside queried period under existing visibility policy; selection/calculation eligibility unchanged", + "No eligible visible candidate remains explicitly uncertain; no invented input fault" + ], + "verification": { + "new_unit_tests": 5, + "new_unit_assertions": 24, + "targeted_api_tests": 1, + "targeted_api_assertions": 16, + "existing_resolver_regression_tests": 7, + "existing_resolver_regression_assertions": 25, + "typescript": "passed", + "build": "passed, exit0", + "pint": "passed", + "edge": "Normal simulator query, annual2026; missing currency then explicitly chosen USD; inline/summary linked fields focused; source/version disclosure; USD and generic operation preserved after save/recovery; internal code and expression absent from query text", + "no_simulation_created_by_api_diagnostic": true + }, + "qa_fixture": { + "rule_version": 3, + "state_after_proof": "suspended", + "consumer_enabled": false, + "database": "simulator_fiscal_delta_20261021_test", + "imported_into_homologation": false + }, + "test_database": "simulator_fiscal_checks_20261021_test, existing QA schema correction applied before targeted API test; isolated guard preserved", + "proofs": [ + "outputs/simulator-continuation-20261020/iva-diagnostic-api-test.txt", + "outputs/simulator-continuation-20261020/iva-diagnostic-summary.txt", + "outputs/simulator-continuation-20261020/iva-diagnostic-summary.png", + "outputs/simulator-continuation-20261020/iva-diagnostic-build-final.txt" + ], + "main_integrated": false, + "homologation_rule_changed": false, + "legal_coverage_expanded": false } } diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 411e304e..9fa40a99 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -1961,3 +1961,22 @@ Para suspender, desligar `INVESTMOZ_IVA_CONSUMER_ENABLED` e reiniciar graciosame Activação separada aplicada em21/10/2026 às10:55:58UTC, alterando apenas as três chaves do consumidor. Ligação exacta aoID acima, rule_version1 eenabled=true; restante configuração preservada. `/api/v1/fiscal-consumer` confirmouenabled=true porHTTP200; a leitura do resolvedor, sem executar cálculo, confirmouIVA_GENERAL_RATE=16percent, mesmoID/versão/fonte no contextoMZN/internal_general_net de2026. Cinco eventos de governação; contagens de resultados/relatórios preservadas, sem simulações sintéticas no destino. O restauro de segurança e a configuração anterior estão conservados no arquivo privado identificado no recibo. Suspensão concreta: configurar `INVESTMOZ_IVA_CONSUMER_ENABLED=false` e executar o reinício gracioso existente `php8.4 artisan queue:restart` na releaseactual como operador autorizado; ou `POST /api/v2/investment/admin/tax-rules/01a15e1a-5338-700f-8a45-f87707d696a7/suspend`, com `version_no:5` enquanto corrente e evidência verdadeira de pelo menos20caracteres, pela administração autenticada/MFA. Não repetir a suspensão se o estado/versão já tiver mudado. Conservar a ligação e todos os registos; futuras retomas respeitam o estado da regra e o procedimento de versão aplicável, sem escolher automaticamente outra regra. + + +### Diagnóstico da consulta IVA — correcção em QA,21/10/2026 + +O caso reportado identifica «Componentes fiscais e contributivas», IVA_GENERAL_RATE, período01/01/2026–31/12/2026 e «As condições publicadas não correspondem às respostas indicadas». É consulta de parâmetros, não execução do consumidor IVA. O utilizador não forneceu a moeda ou a operação originais; não se afirma que tenha seleccionadoUSD, omitido um campo ou indicado uma operação excluída. + +**Causa confirmada no contrato:** o formulário envia `fiscal_operation`, sem `operation` ou `currency`; a versão publicada exige `operation=internal_general_net` e `currency=MZN`. A validação da consulta nem aceitava esses nomes canónicos. O resolvedor reduzia qualquer condição falhada a `indeterminate_context`; a interface mostrava apenas a frase genérica e o código interno. Esta restrição de ligação é escolha de implementação, não condição organizacional ou jurídica adicional da Tempo. O mesmo contrato reproduziu o estado no período anual reportado, independentemente de se presumir uma moeda no caso original. + +**Correcção delimitada:** cada condição não satisfeita tem categoria, campo, valor recebido e critério esperado, ligados à versão e fonte. Distinguem-se campo em falta, formato inválido, operação/moeda fora do âmbito, versão que não cobre o período e configuração do sistema. Quando não existe versão elegível visível e a evidência não permite separar ausência de cobertura de indisponibilidade de fonte/configuração, a interface mantém essa incerteza; não inventa uma causa nem atribui culpa às respostas. + +A consulta IVA passa a ter moeda explícita (`fiscal_currency`), guardada no rascunho, sem presumirMZN ou copiar moeda do orçamento ou do cálculo IVA. A API recebe esse valor em `context.currency`. A operação genérica não se converte automaticamente no âmbito «operação interna à taxa geral sobre base líquida»: a limitação de integração é explicada com a resposta recebida e o critério esperado, e existe ligação para o formulário IVA com declaração explícita do âmbito. Uma importação/exportação/não residente/reembolso expressamente seleccionado é identificado como exclusão deste lote; não se altera a resposta para a fazer passar. + +As notas aparecem junto dos campos e num resumo com ligações que focam o campo correspondente, mantendo o rascunho. O nome apresentado é «IVA — taxa geral»; «Ver fundamento» contém versão, vigência e fonte, sem expressãoDSL. As falhas de consulta/configuração são identificadas como sistema. O percurso de cálculo e as suas regras de admissão não foram ampliados. A consulta diagnóstica pode observar versões publicadas fora do período seleccionado, sob a mesma política de visibilidade, apenas para explicar a falta de cobertura; não as torna elegíveis. + +**Prova dirigida:**5testes unitários novos/24asserções;1testeAPI/16asserções no clone isolado simulator_fiscal_checks_20261021_test;7testes existentes do resolvedor/25asserções; TypeScript, build(exit0), Pint e diff-check. A primeira execuçãoAPI detectou que esse clone ainda não tinha a coluna fiscal_acceptance; aplicou-se nele a correcção de esquema já aceite, com a guardaQA intacta, e repetiu-se apenas o testeAPI afectado. Não é diagnóstico da base de homologação. A comparação por índice do ensaio foi ajustada à ordem das chavesJSONB; não se alterou o critério fiscal. + +No Edge, simulador normal QA em127.0.0.1:18580: consulta anual2026, operação genérica explicitamente escolhida para o ensaio; moeda inicialmente vazia, depoisUSD escolhido expressamente como caso dirigido. Confirmaram-se notas, resumo, foco em «Rever moeda»/«Rever operação», fundamento da versão sintética3, eUSD/ano/periodicidade/operação conservados após gravação e recuperação do mesmo rascunho. A captura de prova é um ensaioUSD, não evidência deUSD no caso original. Não se executou cálculo nem se exportou relatório. A fixture publicada apenas emQA para observar o diagnóstico foi depois suspensa; o consumidorQA permaneceu desligado. Nenhuma fixture foi importada, regra alterada ou consumidor activado na homologação nesta correcção. + +Provas privadas na pasta `outputs/simulator-continuation-20261020`: iva-diagnostic-api-test.txt, iva-diagnostic-summary.txt, iva-diagnostic-summary.png e iva-diagnostic-build-final.txt. O reciboJSON existente tem o estado detalhado. A candidata permanece na branch `codex/iva-diagnostics-QA`, separada de main; não é uma nova publicação fiscal nem altera a entrega autorizada anteriormente. diff --git a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv index 0fdad18c..cfff3132 100644 --- a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv +++ b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv @@ -10652,7 +10652,7 @@ AUD-SEARCH-T10,Consistência transversal da pesquisa e navegação — Bibliotec AUD-SEARCH-T11,Consistência transversal da pesquisa e navegação — Legislação,Instruções expressas da Tempo na conversa de 20/10/2026; AGENTS.md; RF-MN-010 e AUD-GEO-01 preservados.,navegação/pesquisa,/legislacao,portal/services; portal/document — Reutiliza a correcção da biblioteca e preserva filtros jurídicos/documentais pertinentes.,Sem alteração remota nesta execução; revisão apenas na branch codex/opportunity-search.,HTTP/Inertia: resources.total=0; consumidor e query pública partilhados.,QA — delta delimitado; revisão Tempo pendente,"Situação jurídica, consulta e cálculo continuam independentes.",,"Filtros, contagem, resultados e URL coerentes; universos identificados; erro distinto de vazio; permissões e contexto conservados.",P1,,RF-MN-010; AUD-GEO-01,,,,,,,,,,,Mesmo padrão da biblioteca; não deve expor um universo concorrente.,"Situação jurídica, consulta e cálculo continuam independentes.",portal/services; portal/document; SearchState; SearchFeedback; TerritoryFields; portalNavigation,,"Integração em main apenas após revisão; assistência NLP, contratos e P7 separados.",Correcção/simplificação em QA,,HTTP/Inertia: resources.total=0; consumidor e query pública partilhados.,Pesquisa transversal QA 20/10/2026,20/10/2026,,,,,,,,,,"Provas comuns cobrem o componente reutilizado; HTTP vazio não demonstra operações de gestão ou publicação. Situação jurídica, consulta e cálculo continuam independentes.",,falha funcional; navegação; apresentação visual,,Disponível para revisão na aplicação QA local. AUD-GUIDE-CARD-20261020,"Related educational cards: image, source summary, clear actions",Tempo 20/10/2026; independent delta after 0e7d51f18fb4,educational presentation,/sectores/{slug}; /explorar/{group}; articles; /oportunidades and detail,EducationalArticleCard; EducationalGuides; LegacyContent; SectorInvestment,QA only,1 test / 8 assertions; PHP syntax; TypeScript/build; desktop2048/mobile390; keyboard,QA review pending,Normal QA sector and article verified against existing library resource 10 and local image 25; previous dossier proof retained,,Source-derived summary; public images; conditional opportunity action; context preserved,P1,,,,,,,,,,,,,,,,,,presentation,,,,20/10/2026,,,,,,,,,,,,content; navigation; visual presentation,,Ready for QA review AUD-SIM-INT01,Invalid one-off fiscal date can throw instead of returning validation state,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Fixed in QA; invalid-date checks; 38 directed frontend tests passed,Previous inline-date proof retained; unchanged delta not repeated,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,, | [Saldo consolidado21/10/2026] Concluído no recorte. Nenhum delta de validação de data por demonstrar; prova anterior preservada. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.,,20/10/2026,,,,,,,,,,,,,, -AUD-SIM-INT02,Review actual fiscal parameters sources periods and eligibility,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Verification pending; no parameter activation,Recorte IVA geral sobre base declarada disponibilizado: 4a4d1212 integrado na revisao servida d3fe8a02; regra real v1 publicada e consumidor activo. Nao conclui o simulador fiscal integral.,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,"Nenhuma operacao de disponibilizacao pendente neste lote delimitado. Outras componentes, regimes e coberturas fiscais permanecem fora do recorte; nao repetir provas aceites.",,,"Tempo autorizou incorporacao de 4a4d1212, cadastro/publicacao da regra real e activacao. Mesma administracao real nala@tempo.co.mz, login normal e MFA, sem quatro designacoes; sem parecer juridico ou revisao independente atribuidos a Tempo.",Ligacao de consumidor publicada concluida em QA; decisoes operacionais separadas.,Recorte executado; sem saldo de implementacao ou disponibilizacao identificado neste lote. Nao converter estimativas anteriores em implementacao fiscal integral.," | [Leitura SSH 21/10/2026 08:17 Maputo] Zero regras em simulation_tax_rule_versions na homologação, incluindo todos os estados por leitura administrativa sem ocultação RLS. Seis referências em investment_legal_sources; não são parâmetros executáveis. QA também tinha zero regras. BEGIN READ ONLY/SELECT/ROLLBACK; zero escritas. Prova homologation-fiscal-readonly.json. O git HEAD registado é do checkout consultado, não certifica a revisão do build servido. A ausência de cadastro não é ausência de fontes oficiais nem reabre a aceitação da Tempo de 08/10. Não implementa a ligação funcional ao motor fiscal. | [Saldo consolidado21/10/2026] Parcial. Leitura remota concluída: zero regras de todos os estados na homologação, tal como QA; seis referências jurídicas não são parâmetros executáveis. Falta cadastro fundamentado por componente/âmbito/período e ligação funcional ao motor fiscal. Fontes e análises existentes reutilizáveis; aceitação oficial da Tempo de08/10 preservada. Não activar taxas nem importar/publicar por esta reconciliação. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação. | [Candidata21/10/2026] Diagnóstico fiscal separado no manual existente: fontes/analises já disponíveis; zero parâmetros executáveis na QA e homologação por leitura de todos os estados; governação/resolvedor temporal/DSL existentes; RunSimulation ainda aritmética personalizada sem consumir regras fiscais. Saldo: cadastro fundamentado, orquestração, contrato de resultado/histórico e ligação ao dossier/relatório, com casos por componente/período. Nenhuma regra criada nem activada; implementação fiscal integral aberta e excluída das1–2h de exportações. | [Proposta21/10/2026] Primeiro lote proposto: uma regra IVA geral sobre base liquida/MZN, cobertura demonstrada2026 sem inventar caducidade legal. Reutilizar fontes/analises/governacao/resolvedor/avaliador existentes; arredondamento e ambito aplicavel explicitamente por fundamentar antes de publicar. Cadastro4-8h e codigo/aceitacao12-20h, total16-28h condicionado ao acervo, separado das1-2h de exportacao. Criterios e exclusoes no manual existente. Nenhuma regra cadastrada/activada; nao e implementacao fiscal integral. | [QA IVA v1.2 21/10/2026] Orientacao posterior Tempo: calcular com precisao decimal exacta sem arredondamento automatico, separando apresentacao monetaria; arredondamento generico deixa de ser bloqueio deste lote. Cadastro draft versionado em clone QA; resolvedor/avaliador existentes; proveniencia, historico e relatorio imutavel ligados. 5 feature (51 assercoes), 7 unit (25), 3 frontend; types/build/Pint aprovados. Contexto declarado; nao e saldo a pagar. Zero regras publicadas; sem main/activacao. Recorte QA autorizado verificado (0h engenharia identificada restante); produto fiscal integral continua parcial. Limites e provas no mesmo manual e ficha v1.2; sem atribuicao de validacao juridica ao utilizador. | [Revisao 83d2f97d] Ambito/periodo/exclusoes e procedimentos de integracao, publicacao, activacao e suspensao consolidados no mesmo manual. Lacuna concreta de ecra QA fechada: entradas 1000.03/MZN/2026-10-21; calculo exacto 160.0048; gravacao fa6d17f2-5f47-4cb6-b8cb-40850c12505f; recarga; recuperacao do mesmo ID; relatorio d13e2ad5-3ba8-4d01-9d30-5815496da774 com mesmo valor/proveniencia e hash igual ao resultado. Uma execucao persistida, draft e zero regras publicadas. 1 teste novo de rota/17 assercoes, TypeScript/build e browser aprovados; restantes provas reutilizadas. Interrupcao inicial da ferramenta resolvida, sem atribuicao de falha ao simulador. Revisao do recorte demonstrada; sem nova preparacao generica identificada, main ou activacao; nao fecha aplicabilidade fiscal universal nem simulador integral. | 21/10/2026: proposta do consumidor IVA no simulador existente no manual: falta ligacao a regra publicada e activacao explicita. Integrar codigo desligado, publicar versao e activar consumidor sao tres operacoes distintas. Reutiliza permissoes, governanca, historico e provas aceites; sem ampliar cobertura ou executar estas operacoes. | 21/10/2026: implementado delta de 2ced7b79 no simulador normal QA, activacao desligada por omissao. 8 testes/75 assercoes, 2 testes frontend novos, TypeScript/build/Pint. Edge: indeterminacao, calculo, repeticao sem duplicar, fila admitida antes de suspensao concluida depois, nova execucao recusada e historicos/relatorios preservados. Mesmo manual e recibo consolidam provas e tres operacoes. Fixture isolada suspensa; sem merge ou publicacao/activacao na homologacao. | 21/10/2026: origem da condicao fiscal localizada em e65cb269/servico/CHECKs de 03/10 e nota de implementacao; sem decisao expressa aplicavel encontrada. Correccao QA: administracao existente, mesma pessoa, fonte oficial identificada e eventos preservados. 6 testes/86 assercoes apenas do controlo, sem calculos/exportadores; constraints de outras tabelas iguais. Esquema activo nao alterado; backup/restauro de 171 tabelas confirmado; sem fixture ou publicacao real. | [Execucao autorizada 21/10/2026 10:56 UTC] Servido d3fe8a02ec7f5dcd01984c4f4a1eba18b5a0cf19 contendo 4a4d1212; DDL fiscal aplicado autenticadamente com backup/restauro de171tabelas e registos preservados. Tres fontes reais v1 com hashes/citacoes e aceitacao fiscal Tempo08/10/2026, sem validacao geral automatica. Regra IVA-GERAL-BASE-LIQUIDA rule_version1, ID01a15e1a-5338-700f-8a45-f87707d696a7, published, version_no5, actor15 emtodasetapas;5eventos. Activacao separada ligada a esseID/versao; APIHTTP200 enabled=true e resolvedor16percent no contexto delimitado sem calculo novo. FixtureQA nao importada; calculos/exportadores/PDF aceites nao repetidos. Suspensao de novas admissoes conserva snapshots/historicos/relatorios. Recibo e manual existentes actualizados; limitacoes de abertura de consola/extensao registadas como ferramenta.",,20/10/2026,,,,,,,,,,,,,, +AUD-SIM-INT02,Review actual fiscal parameters sources periods and eligibility,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Verification pending; no parameter activation,Recorte IVA geral sobre base declarada disponibilizado: 4a4d1212 integrado na revisao servida d3fe8a02; regra real v1 publicada e consumidor activo. Nao conclui o simulador fiscal integral.,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,"Nenhuma operacao de disponibilizacao pendente neste lote delimitado. Outras componentes, regimes e coberturas fiscais permanecem fora do recorte; nao repetir provas aceites.",,,"Tempo autorizou incorporacao de 4a4d1212, cadastro/publicacao da regra real e activacao. Mesma administracao real nala@tempo.co.mz, login normal e MFA, sem quatro designacoes; sem parecer juridico ou revisao independente atribuidos a Tempo.",Ligacao de consumidor publicada concluida em QA; decisoes operacionais separadas.,Recorte executado; sem saldo de implementacao ou disponibilizacao identificado neste lote. Nao converter estimativas anteriores em implementacao fiscal integral.," | [Leitura SSH 21/10/2026 08:17 Maputo] Zero regras em simulation_tax_rule_versions na homologação, incluindo todos os estados por leitura administrativa sem ocultação RLS. Seis referências em investment_legal_sources; não são parâmetros executáveis. QA também tinha zero regras. BEGIN READ ONLY/SELECT/ROLLBACK; zero escritas. Prova homologation-fiscal-readonly.json. O git HEAD registado é do checkout consultado, não certifica a revisão do build servido. A ausência de cadastro não é ausência de fontes oficiais nem reabre a aceitação da Tempo de 08/10. Não implementa a ligação funcional ao motor fiscal. | [Saldo consolidado21/10/2026] Parcial. Leitura remota concluída: zero regras de todos os estados na homologação, tal como QA; seis referências jurídicas não são parâmetros executáveis. Falta cadastro fundamentado por componente/âmbito/período e ligação funcional ao motor fiscal. Fontes e análises existentes reutilizáveis; aceitação oficial da Tempo de08/10 preservada. Não activar taxas nem importar/publicar por esta reconciliação. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação. | [Candidata21/10/2026] Diagnóstico fiscal separado no manual existente: fontes/analises já disponíveis; zero parâmetros executáveis na QA e homologação por leitura de todos os estados; governação/resolvedor temporal/DSL existentes; RunSimulation ainda aritmética personalizada sem consumir regras fiscais. Saldo: cadastro fundamentado, orquestração, contrato de resultado/histórico e ligação ao dossier/relatório, com casos por componente/período. Nenhuma regra criada nem activada; implementação fiscal integral aberta e excluída das1–2h de exportações. | [Proposta21/10/2026] Primeiro lote proposto: uma regra IVA geral sobre base liquida/MZN, cobertura demonstrada2026 sem inventar caducidade legal. Reutilizar fontes/analises/governacao/resolvedor/avaliador existentes; arredondamento e ambito aplicavel explicitamente por fundamentar antes de publicar. Cadastro4-8h e codigo/aceitacao12-20h, total16-28h condicionado ao acervo, separado das1-2h de exportacao. Criterios e exclusoes no manual existente. Nenhuma regra cadastrada/activada; nao e implementacao fiscal integral. | [QA IVA v1.2 21/10/2026] Orientacao posterior Tempo: calcular com precisao decimal exacta sem arredondamento automatico, separando apresentacao monetaria; arredondamento generico deixa de ser bloqueio deste lote. Cadastro draft versionado em clone QA; resolvedor/avaliador existentes; proveniencia, historico e relatorio imutavel ligados. 5 feature (51 assercoes), 7 unit (25), 3 frontend; types/build/Pint aprovados. Contexto declarado; nao e saldo a pagar. Zero regras publicadas; sem main/activacao. Recorte QA autorizado verificado (0h engenharia identificada restante); produto fiscal integral continua parcial. Limites e provas no mesmo manual e ficha v1.2; sem atribuicao de validacao juridica ao utilizador. | [Revisao 83d2f97d] Ambito/periodo/exclusoes e procedimentos de integracao, publicacao, activacao e suspensao consolidados no mesmo manual. Lacuna concreta de ecra QA fechada: entradas 1000.03/MZN/2026-10-21; calculo exacto 160.0048; gravacao fa6d17f2-5f47-4cb6-b8cb-40850c12505f; recarga; recuperacao do mesmo ID; relatorio d13e2ad5-3ba8-4d01-9d30-5815496da774 com mesmo valor/proveniencia e hash igual ao resultado. Uma execucao persistida, draft e zero regras publicadas. 1 teste novo de rota/17 assercoes, TypeScript/build e browser aprovados; restantes provas reutilizadas. Interrupcao inicial da ferramenta resolvida, sem atribuicao de falha ao simulador. Revisao do recorte demonstrada; sem nova preparacao generica identificada, main ou activacao; nao fecha aplicabilidade fiscal universal nem simulador integral. | 21/10/2026: proposta do consumidor IVA no simulador existente no manual: falta ligacao a regra publicada e activacao explicita. Integrar codigo desligado, publicar versao e activar consumidor sao tres operacoes distintas. Reutiliza permissoes, governanca, historico e provas aceites; sem ampliar cobertura ou executar estas operacoes. | 21/10/2026: implementado delta de 2ced7b79 no simulador normal QA, activacao desligada por omissao. 8 testes/75 assercoes, 2 testes frontend novos, TypeScript/build/Pint. Edge: indeterminacao, calculo, repeticao sem duplicar, fila admitida antes de suspensao concluida depois, nova execucao recusada e historicos/relatorios preservados. Mesmo manual e recibo consolidam provas e tres operacoes. Fixture isolada suspensa; sem merge ou publicacao/activacao na homologacao. | 21/10/2026: origem da condicao fiscal localizada em e65cb269/servico/CHECKs de 03/10 e nota de implementacao; sem decisao expressa aplicavel encontrada. Correccao QA: administracao existente, mesma pessoa, fonte oficial identificada e eventos preservados. 6 testes/86 assercoes apenas do controlo, sem calculos/exportadores; constraints de outras tabelas iguais. Esquema activo nao alterado; backup/restauro de 171 tabelas confirmado; sem fixture ou publicacao real. | [Execucao autorizada 21/10/2026 10:56 UTC] Servido d3fe8a02ec7f5dcd01984c4f4a1eba18b5a0cf19 contendo 4a4d1212; DDL fiscal aplicado autenticadamente com backup/restauro de171tabelas e registos preservados. Tres fontes reais v1 com hashes/citacoes e aceitacao fiscal Tempo08/10/2026, sem validacao geral automatica. Regra IVA-GERAL-BASE-LIQUIDA rule_version1, ID01a15e1a-5338-700f-8a45-f87707d696a7, published, version_no5, actor15 emtodasetapas;5eventos. Activacao separada ligada a esseID/versao; APIHTTP200 enabled=true e resolvedor16percent no contexto delimitado sem calculo novo. FixtureQA nao importada; calculos/exportadores/PDF aceites nao repetidos. Suspensao de novas admissoes conserva snapshots/historicos/relatorios. Recibo e manual existentes actualizados; limitacoes de abertura de consola/extensao registadas como ferramenta. | [Diagnostico IVA QA21/10/2026] Falha estrutural reproduzida na consulta anual: fiscal_operation nao satisfaz operation e moeda nao era comunicada. Sem presumir respostas originais. Cada condicao tem campo/recebido/esperado/categoria; notas junto ao campo, resumo com foco e Ver fundamento semDSL/codigo. Moeda fiscal explicita, sem conversao/copia.5unitarios24assercoes+1API16assercoes, Edge comUSD dirigido preservado apos recuperacao; fixtureQA v3suspensa, consumidorQAoff. Nao altera regra/activacao da homologacao; calculos e exportadores nao repetidos. Branch codex/iva-diagnostics-QA.",,20/10/2026,,,,,,,,,,,,,, AUD-SIM-INT03,Review isolation recovery across projects and sessions,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Existing controls and accepted proofs to reconcile; no leak established,Two dossiers and new login recovered in browser; cashflow persistence corrected and HTTP-tested; new visual cashflow delta pending,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,,"[Retoma 21/10/2026] QA 18576, clone simulator_review_delta_20261020_test: sessão sintética renovada por login normal; gravação autenticada e recarga recuperaram saldo 123, receita 900, custos 600, calendário mensal 10/vazio/0, crédito IVA estimado 500. Caixa conhecida 3713 MZN; total indeterminado desde mês 2; crédito estimado excluído da caixa. Prova visual cashflow-recovered-20261021.png e snapshot .txt em outputs/simulator-continuation-20261020. Não certifica recuperação destes novos valores após outro login, CSV/DOCX ou PDF nativo. Ligação de controlo do navegador perdida durante espera de download; não atribuir falha à aplicação. Leitura remota aguarda identificação da ligação SSH; zero alterações no destino. | [Saldo consolidado21/10/2026] Concluído no recorte. A/B, logout/novo login, recuperação e recusa pela aplicação demonstrados anteriormente. A tesouraria tem prova API de nova sessão e prova visual de recuperação por recarga. Não atribuir ao navegador nova sessão dos valores de tesouraria, nem nova certificação RLS; limites expressos conservados. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,, AUD-SIM-INT04,Review stale and historical results and correct dossier,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Existing versions/fingerprints; targeted verification pending,Historical results visible after fix; 12 and 24 recovered; hashes unchanged; synthetic temporal resolver versions verified in memory only,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P1,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [Saldo consolidado21/10/2026] Concluído no recorte demonstrado. Histórico24/12 visível, hashes preservados e versões temporais sintéticas do resolvedor conferidas. Execução fiscal integrada com parâmetros reais pertence ao saldo de02 e não foi demonstrada por estes testes. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,, AUD-SIM-INT05,Independent financial controls unknown zero units and currencies,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Reconciliation pending; no invented tax assumptions,Independent business-plan and cashflow ledgers passed; complete credit recipe included in screen and exports,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P1,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,,"[Retoma 21/10/2026] QA 18576, clone simulator_review_delta_20261020_test: sessão sintética renovada por login normal; gravação autenticada e recarga recuperaram saldo 123, receita 900, custos 600, calendário mensal 10/vazio/0, crédito IVA estimado 500. Caixa conhecida 3713 MZN; total indeterminado desde mês 2; crédito estimado excluído da caixa. Prova visual cashflow-recovered-20261021.png e snapshot .txt em outputs/simulator-continuation-20261020. Não certifica recuperação destes novos valores após outro login, CSV/DOCX ou PDF nativo. Ligação de controlo do navegador perdida durante espera de download; não atribuir falha à aplicação. Leitura remota aguarda identificação da ligação SSH; zero alterações no destino. | [Saldo consolidado21/10/2026] Concluído nos controlos delimitados. Controlos independentes de caixa/plano/crédito, zero/desconhecido e moedas preservados. Não são validação de taxas legais nem certificação de toda a contabilidade. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,, diff --git a/resources/js/components/simulation-fiscal-status.tsx b/resources/js/components/simulation-fiscal-status.tsx index 846a3b52..94aa4056 100644 --- a/resources/js/components/simulation-fiscal-status.tsx +++ b/resources/js/components/simulation-fiscal-status.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { fiscalComponentCodes } from '@/lib/simulation-contract'; +import { ivaIssueText, type IvaDiagnostic, type IvaIssue } from '@/lib/iva-diagnostics'; type Parameter = { parameter_code: string; @@ -56,6 +57,8 @@ export function SimulationFiscalStatus({ periodStart, periodEnd, context, + onDiagnostics, + onCorrect, }: { salaryView?: string; scenarioMode?: string; @@ -63,8 +66,11 @@ export function SimulationFiscalStatus({ periodStart?: string; periodEnd?: string; context?: Record; + onDiagnostics?: (issues: IvaIssue[]) => void; + onCorrect?: (field: string) => void; }) { const [parameters, setParameters] = useState([]); + const [diagnostics, setDiagnostics] = useState([]); const [requestState, setRequestState] = useState('idle'); const [retry, setRetry] = useState(0); @@ -79,10 +85,14 @@ export function SimulationFiscalStatus({ !periodEnd ) { setParameters([]); + setDiagnostics([]); + onDiagnostics?.([]); setRequestState('idle'); return; } const abort = new AbortController(); + setDiagnostics([]); + onDiagnostics?.([]); setRequestState('loading'); void fetch('/api/v1/tax-parameters/resolve', { method: 'POST', @@ -104,11 +114,15 @@ export function SimulationFiscalStatus({ if (!response.ok) throw new Error('lookup_failed'); const payload = (await response.json()) as { parameters?: Parameter[]; + diagnostics?: IvaDiagnostic[]; }; if (abort.signal.aborted) return; setParameters( Array.isArray(payload.parameters) ? payload.parameters : [], ); + const details = Array.isArray(payload.diagnostics) ? payload.diagnostics : []; + setDiagnostics(details); + onDiagnostics?.(details.flatMap((detail) => detail.issues)); setRequestState('done'); }) .catch((error: unknown) => { @@ -158,7 +172,7 @@ export function SimulationFiscalStatus({ {requestState === 'loading' ? 'A consultar parâmetros aprovados para o período e âmbito indicados…' : requestState === 'error' - ? 'Não foi possível consultar os parâmetros; o resultado permanece indeterminado.' + ? 'Falha na consulta do sistema: não foi possível obter os parâmetros. O problema não é atribuído às respostas; estas foram preservadas. Tente novamente.' : !periodStart || !periodEnd ? 'Indique um período exacto. Para periodicidade mensal ou trimestral, seleccione o mês ou trimestre.' : `Período consultado: ${periodStart} a ${periodEnd}.`} @@ -186,6 +200,22 @@ export function SimulationFiscalStatus({ {salaryViewMessages[salaryView]}

)} + {diagnostics.length > 0 &&
+

IVA — taxa geral: condições não satisfeitas

+

Não foi possível resolver este parâmetro. As respostas foram preservadas; esta consulta não executa o cálculo do imposto.

+ {diagnostics.map((detail, index) =>
+ +
Ver fundamento +

IVA — taxa geral · versão {detail.rule_version}. Vigência: {detail.validity.start} a {detail.validity.end ?? 'sem data final indicada'}.

+

{detail.source.title} — {detail.source.diploma}, {detail.source.article}

+ {detail.source.url?.startsWith('https://') && Consultar fonte} +
+
)} +
}
    {fiscalComponentCodes.map((code) => { const selected = @@ -211,13 +241,13 @@ export function SimulationFiscalStatus({

    ) : parameters.length === 0 ? (

    - Indeterminado — nenhum parâmetro publicado + {code === 'IVA' ? 'Indeterminado — não existe uma versão elegível visível nesta consulta. Consulte o diagnóstico do período acima, quando disponível. Sem esse diagnóstico, não é possível distinguir ausência de cobertura de indisponibilidade da regra ou da fonte; não se atribui o problema às suas respostas.' : <>Indeterminado — nenhum parâmetro publicado aplicável. Faltam parâmetros aprovados para{' '} {code}, no período {periodStart} a{' '} {periodEnd} e no âmbito indicado. A consulta não identifica um conjunto obrigatório de parâmetros; não é possível determinar o - imposto nem substituir as lacunas por zero. + imposto nem substituir as lacunas por zero.}

    ) : (
    @@ -227,7 +257,7 @@ export function SimulationFiscalStatus({ className="border-t pt-2" > - {parameter.parameter_code} + {parameter.parameter_code === 'IVA_GENERAL_RATE' ? 'IVA — taxa geral' : parameter.parameter_code} {parameter.status === 'resolved' ? ( <> @@ -238,10 +268,10 @@ export function SimulationFiscalStatus({

    Regra{' '} - {parameter.rule_code} v + {parameter.parameter_code === 'IVA_GENERAL_RATE' ? 'IVA — taxa geral' : parameter.rule_code} v {parameter.rule_version} ; âmbito{' '} - {parameter.scope_key} + {parameter.parameter_code === 'IVA_GENERAL_RATE' ? 'operação interna geral sobre base líquida em MZN' : parameter.scope_key}

    Vigência:{' '} @@ -279,13 +309,13 @@ export function SimulationFiscalStatus({ target="_blank" rel="noreferrer" > - Consultar fundamento + Ver fundamento )} ) : (

    - {labels[parameter.status] ?? + {(parameter.parameter_code === 'IVA_GENERAL_RATE' && diagnostics.length ? 'Consulte acima cada condição e o fundamento da versão.' : labels[parameter.status]) ?? 'Indeterminado — não foi possível validar o parâmetro.'}

    )} diff --git a/resources/js/components/simulation-form.tsx b/resources/js/components/simulation-form.tsx index 5500ae73..d6955ee1 100644 --- a/resources/js/components/simulation-form.tsx +++ b/resources/js/components/simulation-form.tsx @@ -56,6 +56,7 @@ import { SimulationFinancialTools } from '@/components/simulation-financial-tool import { SimulationCashflowPlanner } from '@/components/simulation-cashflow-planner'; import { SimulationBusinessPlan } from '@/components/simulation-business-plan'; import { SimulationFiscalStatus } from '@/components/simulation-fiscal-status'; +import { ivaIssueText, type IvaIssue } from '@/lib/iva-diagnostics'; import { ProjectMilestonePlanner, ProjectTeamPlanner, @@ -810,6 +811,7 @@ export function SimulationForm() { }); const [step, setStep] = useState(0); const [errors, setErrors] = useState>({}); + const [ivaIssues, setIvaIssues] = useState([]); const [serverFieldErrors, setServerFieldErrors] = useState< Record >({}); @@ -1936,6 +1938,7 @@ export function SimulationForm() { ) { if (!ready) return; setContinueLaterSaved(false); + setIvaIssues((previous) => previous.filter((issue) => issue.field !== key)); setServerFieldErrors((previous) => { const next = { ...previous }; delete next[key]; @@ -3285,6 +3288,7 @@ export function SimulationForm() { : Boolean(field.required); const fieldError = serverFieldErrors[field.key] ?? errors[field.key]; + const ivaNotes = ivaIssues.filter((issue) => issue.field === field.key).map((issue, index) =>

    {ivaIssueText(issue)}

    ); if (field.options) { const parentKey = field.key === 'district_id' @@ -3331,6 +3335,7 @@ export function SimulationForm() { } /> + {ivaNotes}
    ); } @@ -3405,6 +3410,7 @@ export function SimulationForm() { {fieldError}

    )} + {ivaNotes} {field.key === 'reduction' && (

    Hipótese sua, não representa um @@ -3609,6 +3615,12 @@ export function SimulationForm() { aria-label="Consulta de parâmetros fiscais" > { + const target = journeySections.findIndex((section) => (simulationFieldGroups[section.id] ?? []).some((field) => field.key === key)); + if (target >= 0) setStep(target); + window.requestAnimationFrame(() => document.getElementById(`field-${key}`)?.focus()); + }} salaryView={String(draft.irps_salary_view ?? '')} scenarioMode={ openedTool === 'fiscal' @@ -3618,7 +3630,7 @@ export function SimulationForm() { component={String(draft.fiscal_component ?? '')} periodStart={getFiscalPeriodRange(draft)?.start} periodEnd={getFiscalPeriodRange(draft)?.end} - context={Object.fromEntries( + context={{ ...Object.fromEntries( [ 'fiscal_periodicity', 'fiscal_operation', @@ -3635,7 +3647,7 @@ export function SimulationForm() { ? [[key, draft[key] as string]] : [], ), - )} + ), ...(typeof draft.fiscal_currency === 'string' && draft.fiscal_currency !== '' ? {currency: draft.fiscal_currency} : {}) }} /> )} diff --git a/resources/js/components/simulation-published-iva.tsx b/resources/js/components/simulation-published-iva.tsx index a9c484c7..079f2cff 100644 --- a/resources/js/components/simulation-published-iva.tsx +++ b/resources/js/components/simulation-published-iva.tsx @@ -51,8 +51,8 @@ export function SimulationPublishedIva({ busy, onCalculate }: { } catch { /* The execution flow already preserves and reports connection errors. */ } } - return

    -

    IVA geral sobre base líquida

    + return
    +

    IVA — taxa geral

    {message}

    Cobertura: exigibilidade em 2026, operação interna à taxa geral. A base tributável é indicada por si; não é copiada do orçamento. Sem isenções, taxas reduzidas, bases especiais, importações, deduções, declaração ou pagamento. O cálculo não valida o enquadramento jurídico.

    diff --git a/resources/js/lib/iva-diagnostics.ts b/resources/js/lib/iva-diagnostics.ts new file mode 100644 index 00000000..3a260650 --- /dev/null +++ b/resources/js/lib/iva-diagnostics.ts @@ -0,0 +1,26 @@ +export type IvaIssue = { + category: 'missing_field' | 'invalid_value' | 'out_of_scope' | 'period_unavailable' | 'system_configuration'; + field: string | null; + label: string; + received: unknown; + expected: string; + message: string; +}; +export type IvaDiagnostic = { + label: string; + rule_version: number; + validity: { start: string; end: string | null }; + source: { title?: string; diploma?: string; article?: string; url?: string }; + issues: IvaIssue[]; +}; +export const ivaIssueCategories = { + missing_field: 'Campo em falta', invalid_value: 'Valor inválido', out_of_scope: 'Fora do âmbito', + period_unavailable: 'Regra indisponível para o período', system_configuration: 'Configuração do sistema', +}; +export function ivaReceived(value: unknown): string { + const labels: Record = { domestic: 'Operação interna', internal_general_net: 'Operação interna à taxa geral, sobre base líquida', import: 'Importação', export: 'Exportação', refund: 'Reembolso', unknown: 'Não sei', non_resident: 'Operação com não residente', other: 'Outra operação' }; + return typeof value === 'string' && value !== '' ? (labels[value] ?? value) : 'Não recebido'; +} +export function ivaIssueText(issue: IvaIssue): string { + return `${issue.label}: ${ivaIssueCategories[issue.category]}. Recebido: ${ivaReceived(issue.received)}. Esperado: ${issue.expected}. ${issue.message}`; +} diff --git a/resources/js/lib/simulation-form.ts b/resources/js/lib/simulation-form.ts index a64d1327..ccaf52d6 100644 --- a/resources/js/lib/simulation-form.ts +++ b/resources/js/lib/simulation-form.ts @@ -965,6 +965,12 @@ export const simulationFieldGroups: Record< }, ], tax: [ + { + key: 'fiscal_currency', + label: 'Moeda da operação fiscal (sem conversão automática)', + options: 'currency', + showWhen: (d) => d.fiscal_component === 'iva' && d.fiscal_scenario_mode !== 'custom_planning', + }, { key: 'fiscal_period', label: 'Ano do período fiscal a simular', diff --git a/tests/Feature/SimulationPublishedFiscalTest.php b/tests/Feature/SimulationPublishedFiscalTest.php index b7df3fc5..e7add4d4 100644 --- a/tests/Feature/SimulationPublishedFiscalTest.php +++ b/tests/Feature/SimulationPublishedFiscalTest.php @@ -76,6 +76,33 @@ class SimulationPublishedFiscalTest extends TestCase return Simulation::with('reports')->findOrFail($id); } + public function test_iva_query_diagnostics_explain_the_reported_contract_gap_without_calculating_or_changing_answers(): void + { + $count = Simulation::count(); + $input = ['component' => 'IVA', 'period_start' => '2026-01-01', 'period_end' => '2026-12-31', + 'context' => ['fiscal_operation' => 'domestic', 'currency' => 'USD']]; + $this->actingAs($this->consumer)->postJson('/api/v1/tax-parameters/resolve', $input)->assertOk() + ->assertJsonPath('assumptions.currency', 'USD') + ->assertJsonPath('parameters.0.status', 'indeterminate_context') + ->assertJsonPath('diagnostics.0.label', 'IVA — taxa geral') + ->assertJsonPath('diagnostics.0.rule_version', 1) + ->assertJsonPath('diagnostics.0.issues.1.category', 'system_configuration') + ->assertJsonPath('diagnostics.0.issues.1.received', 'domestic') + ->assertJsonPath('diagnostics.0.issues.0.category', 'out_of_scope') + ->assertJsonPath('diagnostics.0.issues.0.received', 'USD') + ->assertJsonPath('diagnostics.0.issues.0.expected', 'MZN'); + unset($input['context']['currency']); + $this->postJson('/api/v1/tax-parameters/resolve', $input)->assertOk() + ->assertJsonPath('diagnostics.0.issues.0.category', 'missing_field'); + $input['context']['currency'] = 'MZN'; + $input['period_start'] = '2022-01-01'; + $input['period_end'] = '2022-12-31'; + $this->postJson('/api/v1/tax-parameters/resolve', $input)->assertOk() + ->assertJsonPath('parameters', []) + ->assertJsonPath('diagnostics.0.issues.0.category', 'period_unavailable'); + self::assertSame($count, Simulation::count()); + } + public function test_normal_simulator_admits_published_rule_and_reuses_history_and_reports(): void { $key = (string) Str::uuid(); diff --git a/tests/Unit/SimulationIvaDiagnosticsTest.php b/tests/Unit/SimulationIvaDiagnosticsTest.php new file mode 100644 index 00000000..49afc872 --- /dev/null +++ b/tests/Unit/SimulationIvaDiagnosticsTest.php @@ -0,0 +1,71 @@ + 'synthetic-qa', 'rule_code' => 'IVA-GERAL-BASE-LIQUIDA', 'rule_version' => 1, + 'component' => 'IVA', 'status' => 'published', 'scope_key' => 'mz.internal-general-net.mzn', + 'valid_from' => '2023-01-01', 'valid_until' => null, 'source_version_no' => 1, + 'source_sha256' => str_repeat('a', 64), 'rule_definition' => ['language' => 'investmoz.tax.v1'], + 'parameters' => ['IVA_GENERAL_RATE' => ['value' => '16', 'unit' => 'percent', 'conditions' => ['operation' => 'internal_general_net', 'currency' => 'MZN']]], + 'source' => ['validation_status' => 'validated', 'version_no' => 1, 'valid_from' => '2023-01-01', 'valid_until' => null, + 'title' => 'Synthetic source', 'diploma' => 'QA only', 'article' => 'Fixture', 'url' => 'https://example.invalid/official']]; + } + + public function test_reported_annual_query_contract_reproduces_the_generic_failure_and_identifies_both_missing_links(): void + { + $version = $this->version(); + $context = ['fiscal_operation' => 'domestic']; + $original = $context; + $before = (new SimulationTaxParameterResolver)->resolveAvailableParameters([$version], 'IVA', '2026-01-01', '2026-12-31', $context); + self::assertSame('indeterminate_context', $before[0]['status']); + $details = (new SimulationIvaDiagnostics)->explain([$version], '2026-01-01', '2026-12-31', $context); + self::assertSame(['system_configuration', 'missing_field'], array_column($details[0]['issues'], 'category')); + self::assertSame('domestic', $details[0]['issues'][0]['received']); + self::assertSame('MZN', $details[0]['issues'][1]['expected']); + self::assertSame($original, $context); + self::assertArrayNotHasKey('rule_definition', $details[0]); + self::assertArrayNotHasKey('rule_code', $details[0]); + } + + public function test_all_failed_conditions_are_separate_and_currency_is_not_assumed(): void + { + foreach (['' => 'missing_field', 'usd' => 'invalid_value', 'USD' => 'out_of_scope'] as $value => $category) { + $details = (new SimulationIvaDiagnostics)->explain([$this->version()], '2026-01-01', '2026-12-31', ['operation' => 'import', 'currency' => $value]); + self::assertCount(2, $details[0]['issues']); + self::assertSame('out_of_scope', $details[0]['issues'][0]['category']); + self::assertSame($category, $details[0]['issues'][1]['category']); + } + } + + public function test_period_and_broken_source_are_not_reported_as_incorrect_answers(): void + { + $version = $this->version(); + $version['source_version_no'] = 2; + $details = (new SimulationIvaDiagnostics)->explain([$version], '2022-01-01', '2022-12-31', ['operation' => 'internal_general_net', 'currency' => 'MZN']); + self::assertSame(['period_unavailable', 'system_configuration'], array_column($details[0]['issues'], 'category')); + self::assertSame('2022-01-01 a 2022-12-31', $details[0]['issues'][0]['received']); + self::assertSame(1, $details[0]['rule_version']); + self::assertSame('Synthetic source', $details[0]['source']['title']); + } + + public function test_matching_context_has_no_diagnostic_and_does_not_evaluate_an_expression(): void + { + self::assertSame([], (new SimulationIvaDiagnostics)->explain([$this->version()], '2026-01-01', '2026-12-31', ['operation' => 'internal_general_net', 'currency' => 'MZN'])); + } + + public function test_an_explicitly_excluded_generic_operation_is_reported_without_inferring_a_domestic_scope(): void + { + $details = (new SimulationIvaDiagnostics)->explain([$this->version()], '2026-01-01', '2026-12-31', ['fiscal_operation' => 'import', 'currency' => 'MZN']); + self::assertCount(1, $details[0]['issues']); + self::assertSame('out_of_scope', $details[0]['issues'][0]['category']); + self::assertSame('import', $details[0]['issues'][0]['received']); + } +}