feat: preparar lote IVA exacto em QA sem activacao

This commit is contained in:
2026-10-21 09:22:11 +02:00
parent 37085b40c1
commit 83d2f97d82
19 changed files with 799 additions and 138 deletions
@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Models\InvestmentLegalSource;
use App\Models\User;
use App\Services\SimulationTaxParameterResolver;
use App\Services\SimulationTaxRuleExpression;
use App\Services\SimulationTaxRuleGovernance;
use App\Support\IsolatedQaDatabase;
use Illuminate\Console\Command;
/** Explicit registration: never run during migration, deployment or document import. */
class RegisterFiscalQaDraft extends Command
{
protected $signature = 'fiscal:qa-register-iva {actor : Existing QA editor ID} {source : Existing accepted legal source UUID}';
protected $description = 'Cadastrar apenas o rascunho IVA do primeiro lote numa base QA isolada, sem publicação';
public function handle(SimulationTaxRuleGovernance $governance): int
{
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 403, 'O cadastro deste lote só é permitido na base QA isolada.');
$actor = User::findOrFail($this->argument('actor'));
$source = InvestmentLegalSource::findOrFail($this->argument('source'));
abort_unless($source->validation_status === 'validated' && $source->diploma === 'Lei 22/2022'
&& $source->article === 'Artigo 17(1)', 422, 'Seleccione a referência aceite da taxa geral, Lei 22/2022, artigo 17(1).');
$payload = json_decode(file_get_contents(database_path('qa/iva-general-net-v1.json')), true, flags: JSON_THROW_ON_ERROR);
$actualHash = hash_file('sha256', base_path('docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf'));
abort_unless(hash_equals($payload['source_sha256'], $actualHash), 422, 'O exemplar não corresponde ao hash da ficha.');
$payload['legal_source_id'] = (string) $source->id;
app(SimulationTaxRuleExpression::class)->validate($payload['rule_definition']);
app(SimulationTaxParameterResolver::class)->validateParameters($payload['parameters']);
$existing = $governance->listing($actor, ['rule_code' => $payload['rule_code']])->getCollection()
->firstWhere('rule_version', 1);
if ($existing) {
foreach ($payload as $field => $value) {
$stored = $existing->attributesToArray()[$field] ?? null;
abort_unless($stored == $value, 409, 'O cadastro existente difere desta ficha; não será sobrescrito.');
}
abort_unless($existing->status === 'draft', 409, 'A versão existente já não é um rascunho.');
$this->line(json_encode(['id' => $existing->id, 'status' => 'draft', 'reused' => true], JSON_THROW_ON_ERROR));
return self::SUCCESS;
}
$rule = $governance->create($actor, $payload);
$this->line(json_encode(['id' => $rule->id, 'status' => $rule->status, 'reused' => false], JSON_THROW_ON_ERROR));
return self::SUCCESS;
}
}
@@ -2,9 +2,11 @@
namespace App\Http\Controllers;
use App\Services\SimulationFiscalQaPreview;
use App\Services\SimulationTaxParameterResolver;
use App\Services\SimulationTaxRuleExpression;
use App\Services\SimulationTaxRuleGovernance;
use App\Support\IsolatedQaDatabase;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
@@ -13,6 +15,24 @@ use InvalidArgumentException;
class SimulationTaxRuleGovernanceController extends Controller
{
public function qaPreview(Request $request, string $id, SimulationFiscalQaPreview $preview): JsonResponse
{
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
$key = $request->header('Idempotency-Key');
abort_unless(is_string($key) && strlen($key) >= 16 && strlen($key) <= 128, 428, 'Idempotency-Key é obrigatório.');
$input = $request->validate([
'base' => ['required', 'string', 'max:40', 'regex:/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/D'],
'reference_date' => ['required', 'date_format:Y-m-d'],
'currency' => ['required', 'in:MZN'],
'operation' => ['required', 'in:internal_general_net'],
]);
$result = $preview->create($request->user(), $id, $input, $key);
$simulation = $result['simulation'];
return response()->json(['data' => $simulation, 'replayed' => $result['replayed']], $result['replayed'] ? 200 : 202)
->header('Location', url('/api/v1/simulations/'.$simulation->id));
}
public function index(Request $request, SimulationTaxRuleGovernance $governance): JsonResponse
{
$filters = $request->validate([
+45 -22
View File
@@ -4,6 +4,7 @@ namespace App\Jobs;
use App\Models\Simulation;
use App\Services\SimulationExecution;
use App\Support\IsolatedQaDatabase;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -62,29 +63,37 @@ class RunSimulation implements ShouldQueue
throw new \RuntimeException('Simulation has no scenario.');
}
$input = json_decode($scenario->parameters, true, flags: JSON_THROW_ON_ERROR);
$row = DB::selectOne('select (CAST(? AS numeric) * CAST(? AS numeric) / 100 * (1 - CAST(? AS numeric) / 100))::text as value', [$input['base'], $input['rate'], $input['reduction']]);
$resultId = (string) Str::uuid();
$engineVersion = 'portal-arithmetic-v1+simulation-v'.$simulation->version_no;
$result = ['base' => $input['base'], 'rate_percent' => $input['rate'], 'reduction_percent' => $input['reduction'], 'scenario_kind' => 'custom_planning', 'indicative_total' => $row->value,
'currency' => null, 'legal_interpretation' => false, 'formula_version' => 'portal-arithmetic-v1',
'contract_version' => 1,
'context' => [
'reference_date' => substr((string) $scenario->created_at, 0, 10),
'currency' => null,
'currency_fraction_digits' => null,
'scenario_id' => (string) $scenario->id,
'scenario_version' => (int) $scenario->version_no,
'engine_version' => $engineVersion,
'ruleset_version' => null,
],
'result' => [
'status' => 'calculated',
'value' => (string) $row->value,
'unit' => null,
'explanation' => 'Resultado aritmético determinístico; não constitui cálculo nem conclusão fiscal.',
'missing_inputs' => [],
'source_ids' => [],
]];
if (($input['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview') {
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
$result = $input['fiscal_preview'];
$engineVersion = 'fiscal-draft-preview-v1';
$result['context']['scenario_id'] = (string) $scenario->id;
$result['context']['scenario_version'] = (int) $scenario->version_no;
} else {
$row = DB::selectOne('select (CAST(? AS numeric) * CAST(? AS numeric) / 100 * (1 - CAST(? AS numeric) / 100))::text as value', [$input['base'], $input['rate'], $input['reduction']]);
$result = ['base' => $input['base'], 'rate_percent' => $input['rate'], 'reduction_percent' => $input['reduction'], 'scenario_kind' => 'custom_planning', 'indicative_total' => $row->value,
'currency' => null, 'legal_interpretation' => false, 'formula_version' => 'portal-arithmetic-v1',
'contract_version' => 1,
'context' => [
'reference_date' => substr((string) $scenario->created_at, 0, 10),
'currency' => null,
'currency_fraction_digits' => null,
'scenario_id' => (string) $scenario->id,
'scenario_version' => (int) $scenario->version_no,
'engine_version' => $engineVersion,
'ruleset_version' => null,
],
'result' => [
'status' => 'calculated',
'value' => (string) $row->value,
'unit' => null,
'explanation' => 'Resultado aritmético determinístico; não constitui cálculo nem conclusão fiscal.',
'missing_inputs' => [],
'source_ids' => [],
]];
}
$json = json_encode($result, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
$canonicalResult = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$json])->canonical;
$resultHash = hash('sha256', $canonicalResult);
@@ -96,11 +105,25 @@ class RunSimulation implements ShouldQueue
DB::table('simulation_jobs')->where('id', $job->id)->update(['status' => 'succeeded', 'finished_at' => now(), 'updated_at' => now()]);
DB::table('simulation_reports')->insert(['id' => (string) Str::uuid(), 'simulation_id' => $simulation->id, 'version_no' => $scenario->version_no,
'status' => 'ready', 'report_snapshot' => $json, 'report_sha256' => $resultHash, 'created_at' => now(), 'updated_at' => now()]);
$sourceJson = json_encode($input, JSON_THROW_ON_ERROR);
$sourceJson = json_encode(isset($result['fiscal_provenance']) ? [
'base' => $result['base'], 'currency' => $result['currency'],
'reference_date' => $result['context']['reference_date'],
'context' => $result['fiscal_provenance']['context'], 'scope_status' => 'user_declared',
] : $input, JSON_THROW_ON_ERROR);
$canonicalSource = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$sourceJson])->canonical;
DB::table('simulation_result_sources')->insertOrIgnore(['id' => (string) Str::uuid(), 'result_id' => $resultId,
'source_type' => 'user_input', 'source_id' => $scenario->id, 'source_version' => (string) $scenario->version_no,
'source_snapshot' => $sourceJson, 'source_sha256' => hash('sha256', $canonicalSource), 'created_at' => now()]);
if (isset($result['fiscal_provenance'])) {
$provenance = $result['fiscal_provenance'];
$provenanceJson = json_encode($provenance, JSON_THROW_ON_ERROR);
$canonicalProvenance = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$provenanceJson])->canonical;
DB::table('simulation_result_sources')->insert([
'id' => (string) Str::uuid(), 'result_id' => $resultId, 'source_type' => 'tax_rule_draft',
'source_id' => $provenance['rule']['id'], 'source_version' => (string) $provenance['rule']['rule_version'],
'source_snapshot' => $provenanceJson, 'source_sha256' => hash('sha256', $canonicalProvenance), 'created_at' => now(),
]);
}
DB::table('simulation_audit_events')->insert(['simulation_id' => $simulation->id, 'actor_user_id' => $simulation->owner_user_id,
'action' => 'simulation.completed', 'correlation_id' => $simulation->id, 'data' => json_encode(['result_sha256' => hash('sha256', $json), 'attempt' => $job->attempts + 1], JSON_THROW_ON_ERROR), 'occurred_at' => now()]);
}, 3);
+26 -5
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Jobs\RunSimulation;
use App\Models\Simulation;
use App\Support\IsolatedQaDatabase;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -17,11 +18,27 @@ final class SimulationExecution
*/
public function create(array $data, ?int $actorId, ?string $organizationId, ?string $token, string $key): array
{
$tokenHash = $actorId === null ? hash_hmac('sha256', (string) $token, (string) config('app.key')) : null;
$scope = $actorId !== null ? (string) $actorId : ($tokenHash ?? 'anonymous');
$scopeHash = hash('sha256', $scope);
$keyHash = hash('sha256', strtolower($key));
$payload = ['base' => (string) $data['base'], 'rate' => (string) $data['rate'], 'reduction' => (string) $data['reduction'], 'scenario_kind' => 'custom_planning', 'organization_id' => $organizationId];
return $this->createPayload($payload, $actorId, $organizationId, $token, $key);
}
public function createFiscalQaPreview(array $snapshot, int $actorId, string $key): array
{
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
abort_unless(($snapshot['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview'
&& in_array($snapshot['result']['status'] ?? null, ['calculated', 'indeterminate'], true)
&& ($snapshot['presentation']['rounding_applied'] ?? null) === false, 422);
return $this->createPayload(['scenario_kind' => 'fiscal_rule_qa_preview', 'fiscal_preview' => $snapshot, 'organization_id' => null],
$actorId, null, null, $key);
}
private function createPayload(array $payload, ?int $actorId, ?string $organizationId, ?string $token, string $key): array
{
$tokenHash = $actorId === null ? hash_hmac('sha256', (string) $token, (string) config('app.key')) : null;
$scopeHash = hash('sha256', $actorId !== null ? (string) $actorId : ($tokenHash ?? 'anonymous'));
$keyHash = hash('sha256', strtolower($key));
ksort($payload);
$payloadHash = hash('sha256', json_encode($payload, JSON_THROW_ON_ERROR));
@@ -44,7 +61,9 @@ final class SimulationExecution
$simulation = Simulation::create([
'id' => $id, 'questionnaire_version_id' => '61000100-0000-4000-8000-000000000001', 'owner_user_id' => $actorId, 'organization_id' => $organizationId,
'anonymous_token_hash' => $tokenHash, 'status' => 'queued', 'version_no' => 1,
'input_snapshot' => $payload, 'calculation_snapshot' => ['engine' => 'portal-arithmetic-v1', 'formula' => 'base * rate / 100 * (1 - reduction / 100)', 'legal_rate_source' => null],
'input_snapshot' => $payload, 'calculation_snapshot' => $payload['scenario_kind'] === 'fiscal_rule_qa_preview'
? ['engine' => 'fiscal-draft-preview-v1', 'mode' => 'qa_draft_only', 'arithmetic' => 'exact_decimal_no_rounding']
: ['engine' => 'portal-arithmetic-v1', 'formula' => 'base * rate / 100 * (1 - reduction / 100)', 'legal_rate_source' => null],
]);
$scenarioId = (string) Str::uuid();
DB::table('simulation_scenarios')->insert([
@@ -85,6 +104,8 @@ final class SimulationExecution
return DB::transaction(function () use ($id, $scenarioId, $data, $request): Simulation {
$this->setContext($request->user()?->id, $this->tokenHash($request));
$simulation = Simulation::whereNull('deleted_at')->lockForUpdate()->findOrFail($id);
abort_if(($simulation->input_snapshot['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview', 422,
'Crie uma nova pré-visualização fiscal; este snapshot conserva a regra e as entradas originais.');
$this->requireMatch($request, $simulation);
$scenario = DB::table('simulation_scenarios')->where('simulation_id', $id)->where('id', $scenarioId)->lockForUpdate()->first();
abort_if($scenario === null, 404);
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\InvestmentLegalSource;
use App\Models\User;
use App\Support\IsolatedQaDatabase;
use Brick\Math\BigRational;
use Illuminate\Support\Facades\DB;
/** Exact operation-level IVA calculation against an explicit QA draft; never a payment/return total. */
final class SimulationFiscalQaPreview
{
public function create(User $actor, string $ruleId, array $input, string $key): array
{
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
return DB::transaction(function () use ($actor, $ruleId, $input, $key): array {
$rule = app(SimulationTaxRuleGovernance::class)->show($actor, $ruleId);
abort_unless($rule->status === 'draft' && $rule->rule_code === 'IVA-GERAL-BASE-LIQUIDA', 422,
'Seleccione o rascunho do primeiro lote fiscal.');
abort_if(collect($rule->rule_definition['nodes'] ?? [])->contains(fn (array $node): bool => ($node['op'] ?? null) === 'round'), 422,
'Este percurso preserva o valor exacto e não aplica arredondamento automático.');
$source = InvestmentLegalSource::findOrFail($rule->legal_source_id);
$version = $rule->attributesToArray();
$version['source'] = $source->attributesToArray();
$date = $input['reference_date'];
$context = ['operation' => $input['operation'], 'currency' => $input['currency']];
$missing = [];
$resolution = [];
$evaluation = null;
if ($date < '2026-01-01' || $date > '2026-12-31') {
$missing[] = 'demonstrated_coverage';
} else {
$resolution = app(SimulationTaxParameterResolver::class)->resolveDraftForQa([$version], 'IVA', $date, $date, $context);
$rate = collect($resolution)->firstWhere('parameter_code', 'IVA_GENERAL_RATE');
if (($rate['status'] ?? null) !== 'resolved' || ($rate['unit'] ?? null) !== 'percent') {
$missing[] = 'eligible_parameter';
} else {
$evaluation = app(SimulationTaxRuleExpression::class)->evaluate($rule->rule_definition,
['base_tributavel' => $input['base']], ['iva_general_rate' => $rate['value']]);
}
}
$exact = ($evaluation['status'] ?? null) === 'determinate'
? (string) BigRational::of($evaluation['result'])->toBigDecimal()
: null;
if ($exact === null && $missing === []) {
$missing = $evaluation['missing'] ?? ['eligible_parameter'];
}
$snapshot = [
'contract_version' => 1, 'scenario_kind' => 'fiscal_rule_qa_preview',
'base' => $input['base'], 'currency' => $input['currency'], 'indicative_total' => $exact,
'legal_interpretation' => false, 'formula_version' => 'investmoz.tax.v1',
'context' => [
'reference_date' => $date, 'currency' => $input['currency'], 'currency_fraction_digits' => 2,
'scenario_id' => null, 'scenario_version' => 1, 'engine_version' => 'fiscal-draft-preview-v1',
'ruleset_version' => $rule->rule_code.':'.$rule->rule_version,
],
'result' => [
'status' => $exact === null ? 'indeterminate' : 'calculated', 'value' => $exact, 'unit' => 'MZN',
'explanation' => $exact === null
? 'IVA indeterminado: o contexto, a fonte ou o período não permitem resolver o parâmetro deste lote QA.'
: 'IVA calculado com precisão decimal exacta, sem arredondamento. Ensaio QA de regra em rascunho; enquadramento declarado, não validado. Não é saldo de declaração nem valor a pagar.',
'missing_inputs' => $missing, 'source_ids' => [(string) $source->id],
],
'presentation' => ['value' => $exact, 'currency' => $input['currency'], 'rounding_applied' => false, 'purpose' => 'operation_tax_not_payment'],
// Exact rational steps supplement the decimal result; no float conversion.
'intermediate' => $evaluation,
'fiscal_provenance' => [
'mode' => 'qa_draft_only', 'scope_status' => 'user_declared', 'context' => $context,
'rounding_policy' => $rule->rounding_policy, 'resolution' => $resolution,
'rule' => $version, 'source_acceptance' => 'Fonte oficial aceite pela decisão Tempo de 08/10/2026; não constitui aprovação jurídica desta operação ou publicação da regra.',
],
];
return app(SimulationExecution::class)->createFiscalQaPreview($snapshot, $actor->id, $key);
});
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Services;
use App\Support\IsolatedQaDatabase;
use Brick\Math\BigRational;
use DateTimeImmutable;
use Illuminate\Support\Facades\DB;
@@ -126,14 +127,30 @@ final class SimulationTaxParameterResolver
* @return list<array<string,mixed>>
*/
public function resolveAvailableParameters(array $versions, string $component, string $periodStart, string $periodEnd, array $context): array
{
return $this->resolveWithStatus($versions, $component, $periodStart, $periodEnd, $context, 'published');
}
/** Explicit draft preview. Never used by the published-parameter API. */
public function resolveDraftForQa(array $versions, string $component, string $periodStart, string $periodEnd, array $context): array
{
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
return $this->resolveWithStatus($versions, $component, $periodStart, $periodEnd, $context, 'draft');
}
private function resolveWithStatus(array $versions, string $component, string $periodStart, string $periodEnd, array $context, string $requiredStatus): array
{
$start = $this->date($periodStart);
$end = $this->date($periodEnd);
if ($end < $start || $start->diff($end)->days > 365) {
throw new InvalidArgumentException('Reveja o período de aplicação indicado.');
}
$groups = [];
foreach ($versions as $version) {
if (($version['component'] ?? null) !== $component
|| ($version['status'] ?? null) !== 'published'
|| ($version['status'] ?? null) !== $requiredStatus
|| ($version['source']['validation_status'] ?? null) !== 'validated'
|| (int) ($version['source_version_no'] ?? 0) !== (int) ($version['source']['version_no'] ?? -1)
|| ! $this->overlaps($version, $start, $end)
+38
View File
@@ -0,0 +1,38 @@
{
"rule_code": "IVA-GERAL-BASE-LIQUIDA",
"rule_version": 1,
"component": "IVA",
"scope_key": "mz.internal-general-net.mzn",
"valid_from": "2023-01-01",
"valid_until": null,
"source_sha256": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53",
"rule_definition": {
"language": "investmoz.tax.v1",
"nodes": {
"base": {"op": "input", "key": "base_tributavel"},
"rate": {"op": "parameter", "key": "iva_general_rate"},
"hundred": {"op": "literal", "value": "100"},
"product": {"op": "multiply", "args": ["base", "rate"]},
"intermediate": {"op": "divide", "args": ["product", "hundred"]}
},
"result": "intermediate"
},
"parameters": {
"IVA_GENERAL_RATE": {
"value": "16",
"unit": "percent",
"conditions": {"operation": "internal_general_net", "currency": "MZN"}
}
},
"rounding_policy": {
"status": "not_applied",
"precision": "exact_decimal",
"method": "none",
"ties": "not_applicable_without_rounding",
"aggregation": "single_declared_taxable_base",
"purpose": "operation_tax_not_payment",
"evidence": "Orientação expressa Tempo: calcular este lote IVA sem arredondamento automático; ficha v1.2",
"coverage_start": "2026-01-01",
"coverage_end": "2026-12-31"
}
}
@@ -1,122 +1,86 @@
# IVA-GERAL-BASE-LIQUIDA — ficha documental v1.1
# IVA-GERAL-BASE-LIQUIDA — ficha documental v1.2
Data: 21/10/2026. Referência: AUD-SIM-INT02 na matriz existente. Origem: proposta `0fafd348`; preparação solicitada pela Tempo. Estado: **rascunho documental para revisão; não cadastrado, não publicado, não activo**. Esta versão identifica fundamento e lacunas; não certifica uma regra executável completa.
21/10/2026. AUD-SIM-INT02 na matriz existente. Proposta inicial `0fafd348`; v1 em `325d58b6`, v1.1 em `37085b40`. Essas versões ficam preservadas em Git. Esta versão aplica a orientação posterior expressa da Tempo: **prosseguir neste lote IVA com precisão decimal exacta, sem introduzir arredondamento automático; separar valor calculado de apresentação monetária**. Retira a falta de uma política genérica de arredondamento como condição bloqueante do cálculo. Não estende a decisão a todos os impostos.
## Âmbito e período
**Estado:** cadastro e cálculo experimental em QA, regra em rascunho, sem publicação/activação ou integração em `main`. Não equivale a conclusão do simulador fiscal, emissão de factura, declaração ou determinação de saldo a pagar. A Tempo declarou não ter conferido a ficha local nem validado juridicamente as conclusões transcritas; não se lhe atribui essa validação.
Percurso proposto: base tributável líquida identificada × taxa geral / 100, para operação interna abrangida pela taxa geral de IVA, em MZN explicitamente indicado. Cobertura de ensaio proposta: 01/01/2026–31/12/2026. É uma delimitação técnica do lote, não uma data de caducidade da lei nem uma restrição organizacional.
## Âmbito e contrato
Não determina automaticamente incidência, qualidade do sujeito passivo, localização, isenção ou regime especial. A base fiscal não pode ser deduzida de uma rubrica de orçamento. Âmbito apenas declarado pelo utilizador deve aparecer como declarado; ausência ou incompatibilidade produz indeterminação. A fundamentação da taxa não prova a sujeição de uma operação concreta.
Um único percurso: base tributável líquida já identificada × taxa geral /100, em MZN explícito, para operação interna abrangida pela taxa geral. Janela demonstrada do lote:01/01/2026–31/12/2026, delimitação técnica que não cria caducidade legal em31/12. Não decide automaticamente incidência, sujeito passivo, localização, isenção ou regime especial. Contexto declarado é rotulado como tal; uma rubrica orçamental não se transforma automaticamente em base fiscal.
Excluídos: imposto já incluído no preço, taxas reduzidas, isenções/listas, importações, digital/não residentes, deduções, crédito/reembolso, incentivos territoriais e outros impostos. Permanecem no inventário existente, sem declaração de cobertura deste lote.
Excluídos: extracção de preço com IVA incluído, taxas reduzidas, isenções/listas, importações, digital/não residentes, dedução/crédito/reembolso, incentivos e outros impostos. Permanecem no inventário documental existente, sem omissão nem alegação de cobertura deste lote.
## Fontes e aplicação
As fontes oficiais são aceites pela decisão da Tempo de 08/10/2026. Os vínculos abaixo são rastreabilidade e aplicação das fontes; não se exige nova certificação de proveniência. Reutilizar [análise primária](primary-text-review-2025-2026.md), [registo de fontes](official-source-register.md) e [análise da reforma](fiscal-reform-2026-review.md). Exigências históricas genéricas de autenticação adicional ou de um novo papel organizacional não prevalecem sobre essa decisão.
| Elemento | Fundamento / exemplar | Consequência nesta ficha |
|---|---|---|
| Taxa geral 16% | [Lei22/2022 republicada](source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf), art.17(1), página3 do PDF, BR2252-(27). SHA256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. | Valor nominal fundamentado; ainda não é parâmetro executável cadastrado. |
| Eficácia desde 01/01/2023 | Mesmo exemplar, art.6 da Lei22/2022, página4 do PDF. | Preservar início legal; não substituir pelo início da cobertura de ensaio de2026. |
| Alterações para2026 | [BR248, suplemento1/2025](source-archive/BR-248-I-Suplemento-1-2025.pdf), Lei10/2025, art.1 na página12 e art.5 na página15. SHA256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. | Eficácia em01/01/2026; lista de artigos alterados não inclui17. Não equivale a consolidação completa de incidência/regimes. |
| Preço com imposto incluído | Mesmo BR248, art.34 do CIVA, página14: divisão por116 ×100 e arredondamento do resultado para a unidade mais próxima. | Regula extracção da base a partir do preço com imposto. Fora deste percurso. Não fundamenta arredondamento do imposto calculado sobre base líquida; não inferir tratamento de empates. |
| Rectificação de2017 | [BR89/2017](source-archive/BR-89-I-Serie-2017-06-08.pdf), aviso relativo à Lei13/2016; análise existente identifica art.6(5)c/d,14(2)c e anexos de isenção. | Preservar cadeia documental. A taxa geral não dispensa determinar enquadramento da operação. Listas de isenção não foram consolidadas por esta ficha. |
Foi recuperado do inventário existente o exemplar `Lei 13_2016_Reimpressao Codigo do IVA.pdf`, em `\\Mac\Home\Documents\TEMPO\Portal Tempo\Legislacao\`, 32páginas, 2.127.554bytes, SHA256 `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. É digitalizado, sem camada textual nas32páginas. A identificação e integridade foram registadas; não se afirma leitura integral nem fundamento de âmbito/arredondamento a partir deste exemplar nesta versão. Não o confundir com outra cópia de15páginas mencionada em análises anteriores. O cotejo visual dos dispositivos relevantes e das alterações/regulamentação aplicáveis continua trabalho documental concreto, sem revalidar a credibilidade oficial nem repetir todo o inventário.
## Parâmetros, lacunas e contrato de resultado
| Campo | Situação |
| Campo | Estado actual |
|---|---|
| Identificador / versão | `IVA-GERAL-BASE-LIQUIDA` / documental-v1.1; identificador provisório, sem UUID de base. |
| Taxa / unidade | 16 / percent, fundamento acima. |
| Base / moeda / data | Entradas obrigatórias, com origem e contexto; MZN explícito, sem conversão implícita. Não substituir ausência por zero. |
| Enquadramento | Identificação de operação interna à taxa geral e estado declarado/comprovado. Falta fechar correspondência normativa das condições do percurso. |
| Arredondamento | **Indeterminado nesta versão:** precisão, modo, empates e incidência por linha/documento/apuramento não fundamentados para base líquida. Não adoptar HALF_UP ou duas casas por conveniência técnica. |
| Expressão conceptual | `base_tributavel * taxa_percentual / 100`; não constitui definição DSL publicada. Conservar exactidão intermédia; sem total fiscal final enquanto faltar política aplicável. |
| Cobertura | Ensaio2026; vigência legal separada e sem expiração artificial. Fora da cobertura demonstrada é mensagem de cobertura, não revogação da lei. |
| Proveniência | Resultado deverá fixar regra/versão, fonte/hash/artigo, data/período, entradas/unidades, contexto e seu estado, passos, arredondamento e lacunas. |
| Regra | `IVA-GERAL-BASE-LIQUIDA`, versão1, estado `draft`; cadastro explícito apenas em QA. |
| Taxa / unidade | `16` / `percent`, vínculo Lei22/2022, art.17(1), exemplar/hash abaixo. |
| Vigência cadastrada | Desde01/01/2023, sem termo artificial; cobertura ensaiada2026 separada da vigência. |
| Entradas | Base decimal como texto, MZN explícito, data de exigibilidade declarada, contexto `internal_general_net`. Zero explícito é válido; ausência não é zero. |
| Valor calculado | Decimal exacto obtido pelo avaliador racional existente; sem conversão para float, truncagem ou redução para duas casas. |
| Apresentação | Valor decimal preservado como texto e moeda separados. Duas casas da unidade monetária não impõem arredondamento ao cálculo. Ecrã/CSV/dossier reproduzem o valor guardado. |
| Arredondamento | `not_applied`; nenhum nó `round` neste percurso. Não se inventa precisão, empate ou método fiscal. |
| Indeterminação efectiva | Fonte/versão/contexto incompatível, parâmetro ausente ou pedido fora da cobertura demonstrada. A falta de arredondamento não torna indeterminado o resultado deste percurso. |
| Limites do resultado | Cálculo QA sobre regra em rascunho e enquadramento declarado; não é obrigação validada nem saldo a pagar. |
Casos documentais iniciais, sem pretender validar incidência: base100MZN e taxa16% dão intermédio exacto16MZN; base0,01MZN dá intermédio0,0016MZN, cujo valor fiscal final fica indeterminado sem política. São referências aritméticas, não provas de obrigação fiscal. Acrescentar casos independentes de fronteira e empates depois de fundamentar a política.
## Fontes reutilizadas e disposições documentadas
## Integração e estimativa separadas
Fontes oficiais aceites pela decisão Tempo de08/10/2026; sem nova certificação genérica de proveniência. A identificação de âmbito, período e dispositivo é aplicação da fonte aceite. Reutilizar [análise primária](primary-text-review-2025-2026.md), [registo de fontes](official-source-register.md) e [reforma fiscal](fiscal-reform-2026-review.md). A aceitação da fonte não activa regra nem valida juridicamente uma operação concreta.
Reutilizar `SimulationTaxRuleGovernance`, `SimulationTaxParameterResolver` e `SimulationTaxRuleExpression`. Não reconstruir governação, resolvedor temporal ou avaliador. O fluxo actual de `RunSimulation` é planeamento personalizado; não o renomear como resultado fiscal verificado.
| Exemplar | Identificação e integridade |
|---|---|
| [Lei13/2016 / CIVA republicado](source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf) | BR156,10.º suplemento,30/12/2016;32páginas,2.127.554bytes; SHA256 `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. Leitura visual dirigida PDF6–8,13–14,17–18; entrada em vigor no art.4/PDF5. Não se afirma análise integral de anexos. |
| [Lei22/2022 republicada](source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf) | BR250,2.º suplemento;18páginas,1.834.284bytes; SHA256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. Art.17(1),PDF3/BR2252-(27), taxa16%; art.6 da Lei,PDF4, eficácia01/01/2023. |
| [BR248, suplemento1/2025](source-archive/BR-248-I-Suplemento-1-2025.pdf) |21páginas,440.018bytes; SHA256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. Lei10/2025,PDF12–15; eficácia01/01/2026. |
| [BR16/2008 / Decreto7/2008](source-archive/BR-16-I-Serie-2008-04-16.pdf) |16/04/2008;28páginas,2.267.865bytes; SHA256 `FC373FF5637EDF75EC6A9595DEC8763A8503D4EE786E3B7D8F1C742EAE9D80EE`. Regulamento IVA,PDF1–2; página2 conferida visualmente. O BR inclui outros impostos, que não são fundamento deste lote. |
| [BR89/2017 / rectificação CIVA](source-archive/BR-89-I-Serie-2017-06-08.pdf) | Aviso relativo a art.6(5)c/d,14(2)c e anexos de isenção; análise anterior reutilizada. Não encerra consolidação das listas de isenção. |
| Frente | Trabalho restante do lote | Estimativa proposta |
| Disposição exacta | Conteúdo documentado / consequência |
|---|---|
| CIVA art.1(1)(a), PDF6, BR1426-(264) | Transmissões de bens e prestações de serviços onerosas no território nacional, nos termos do art.6, por sujeito passivo nessa qualidade. Exigir identificação desse contexto; importações ficam fora. |
| Art.2(1)(a), PDF6 | Qualidade do sujeito passivo ligada às condições de residência/estabelecimento e actividade independente previstas. Perfil de investidor não prova sujeição. |
| Arts.3(1),4(1), PDF6–7 | Transferência onerosa de bens corpóreos e noção geral de serviço. Casos assimilados/especiais não entram implicitamente na fórmula. |
| Art.6(1), PDF7, BR1426-(265) | Para bens, localização no início do transporte/expedição ou quando postos à disposição, sem transporte. Serviços seguem art.6(3) e excepções subsequentes; não basta localização do prestador para todos os serviços. |
| Art.15(1), PDF13, BR1426-(271) | Base geral: contraprestação obtida ou a obter, sem prejuízo das bases especiais do n.º2. Não equivale a qualquer receita de planeamento. |
| Art.15(5)(a)/(b), PDF14, BR1426-(272) | Inclusão de outros impostos/direitos/taxas, excepto o próprio IVA, e despesas acessórias debitadas, nas condições previstas. |
| Art.15(6)(a)–(d), PDF14 | Exclusões condicionadas: indemnizações judiciais indicadas, descontos/abatimentos/bónus, pagamentos em nome/conta de terceiros registados adequadamente e embalagens retornáveis. Não classificar só pelo nome da rubrica. |
| Art.17(2), PDF14; art.7(1)(a)/(b), PDF8 | Taxa vigente na exigibilidade; facto gerador/exigibilidade diferem por operação e condições. A criação da simulação não substitui a data fiscal. Adiantamentos e prestações continuadas requerem enquadramento próprio. |
| Art.27(5)(c)/(d), PDF17, BR1426-(275) | Separação do preço líquido/elementos da base, taxa e montante do imposto. Lei10/2025 preserva essas alíneas com «[…]», alterando o n.º10. |
| Regulamento, arts.4/5, Decreto7/2008,PDF1–2 | Remete matéria colectável para arts.15/16 e apuramento devido para arts.18–22 do Código. IVA da operação não equivale ao saldo após deduções. |
Cadeia temporal documentada: Lei13/2016 efectiva em01/01/2017; a taxa histórica17% do exemplar não é usada para2026. Lei22/2022 altera art.17(1) para16% desde01/01/2023 e acrescenta art.15(10) para taxa reduzida, mantendo os números gerais consultados. Lei10/2025, efectiva01/01/2026, não lista arts.15/17 entre os alterados. Esta conclusão documental não é atribuída como validação jurídica ao utilizador e não certifica todos os regimes/excepções.
## Precisão do cálculo e exigências contextuais
Art.34 do CIVA, Lei10/2025/PDF14, regula extracção de base de valores com imposto incluído, com divisor116 e arredondamento para unidade mais próxima. Art.9(1)(b) do regulamento/Decreto7/2008,PDF2/p.100, trata do valor do meio de pagamento por cheque ou vale de correio e arredondamento por excesso para meticais nesse exemplar histórico. **Nenhum é aplicado como regra geral de arredondamento deste cálculo sobre base líquida.** A vigência consolidada e exigências de formatos de pagamento/declaração serão determinadas quando esses consumidores forem abrangidos.
A [comunicação oficial de30/09/2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/) anuncia alterações regulamentares; não fornece articulado publicado e eficácia por disposição. A pesquisa dirigida em fontes oficiais não identificou uma exigência geral que obrigue a arredondar este percurso. Ausência de correspondência não prova inexistência de instruções específicas. Não há questão pendente à Tempo como pré-condição deste cálculo. Se um futuro formato exigir precisão, apresentar uma única questão contextual, identificando o formato, precisão, método, empates e agregação; não generalizar a outros cálculos.
Nota de rastreabilidade, sem reabrir pontos: o exemplar BR54 de06/04/2017 consultado anteriormente não continha a alteração regulamentar procurada. O [catálogo INM](https://www.inm.gov.mz/pt-br/produtos?page=124) identifica Decreto8/2017 no BR50 de30/03/2017. O articulado e alterações pertinentes continuam a ser correspondências documentais a completar quando necessárias ao consumidor; não são bloqueio genérico do cálculo exacto deste lote.
## Exemplos e provas dirigidas
Pressupostos explícitos: operação interna onerosa à taxa geral, base já apurada, sem isenção/redução/base especial, data coberta2026 e MZN. Não são classificações jurídicas de operações reais.
| Base | IVA calculado exacto | Apresentação / significado |
|---|---|---|
| Aplicação documental e cadastro | Fechar condições e arredondamento com citação; completar versão e casos; transcrever parâmetros/vínculos como rascunho QA quando autorizado. Cadastro não exige criar um novo motor. | 4–8h |
| Código e aceitação técnica | Orquestrar contexto/período → resolvedor → avaliador; resultado e proveniência imutáveis; ligar interface, dossier, histórico e relatórios; ensaios dirigidos. Confirmar necessidade real de alteração de schema antes de a propor. | 12–20h |
|100|16|`16` e moeda `MZN`, separados; não é saldo a pagar. |
|0,03|0,0048|Conservar todas as casas no armazenamento, ecrã e exportação. Não substituir por0,00. |
|0|0|Zero explicitamente declarado, distinto de entrada ausente. |
|9007199254740993,0003|1441151880758558,880048|Preservar sem converter para float JavaScript/PHP. |
|100 + transporte10 − desconto5|16,8 sobre base105|Apenas se verificadas as condições de inclusão/exclusão do art.15. |
Total original:16–28h, estimativa condicional, não medição de horas já consumidas nem novo compromisso de prazo. Espera por elemento documental concreto, revisão e activação não incluídas. Esta ficha não reduz automaticamente a estimativa; recalibrar após fechar as duas lacunas identificadas. As antigas1–2h eram exclusivamente do recorte de exportações já concluído.
Os testes do novo consumidor cobrem registo repetido sem duplicação, rascunho excluído da API publicada, resultado exacto, snapshot/relatório conservados após edição da regra e recuperação noutra sessão de autenticação, acesso negado a outra conta, moeda obrigatória, incompatibilidade da fonte, cobertura e recusa fora de QA. A nova sessão provada é API/autenticação, não alegação de novo ensaio browser. A conta PostgreSQL QA ultrapassa RLS: recusas demonstram a política da aplicação, não nova prova de RLS.
## Aceitação antes de publicação
## Implementação, limites e integração
1. Condições, período e política de arredondamento ligados aos dispositivos aplicáveis; lacunas resolvidas sem valores inventados.
2. No QA isolado, resolução inequívoca por contexto/data, recusa explícita de ausência, ambiguidade e cobertura incompatível; preservar mecanismos temporais existentes.
3. Casos independentes exactos, fraccionários e de fronteira da política; distinguir zero de ausência e exigir moeda/unidades.
4. Snapshot reprodutível com fontes e versões; nova sessão preserva resultado histórico; verificar apenas o novo consumidor quanto ao isolamento.
5. Ecrã e relatório distinguem estimativas do utilizador, contexto declarado e cálculo com regra verificada. Conferir apenas novas linhas/deltas; reutilizar provas aceites de valores, CSV, exportações e paginação.
6. Revisão e publicação pelos mecanismos/papéis existentes da Tempo, separadas do cadastro e da publicação documental. Nenhuma activação nesta preparação.
Reutilizados `SimulationTaxRuleGovernance`, `SimulationTaxParameterResolver`, `SimulationTaxRuleExpression` e o armazenamento existente de simulações, fontes, histórico e relatórios. Não há motor novo, migration ou alteração da base activa. `fiscal:qa-register-iva` exige alvo QA isolado, editor e referência de fonte já existente; confere hash e cadastra apenas rascunho, de forma idempotente. Não corre em deploy/importação. O artefacto versionado é `database/qa/iva-general-net-v1.json`.
A entrega `13f68f1b` — melhorias de planeamento financeiro, recuperação, exportação e idioma — foi integrada após autorização da Tempo e não depende destas lacunas fiscais. `0fafd348` e esta ficha permanecem na branch fiscal, separados de `main`.
Pré-visualização administrativa `POST /api/v2/investment/admin/tax-rules/{id}/qa-preview`, com `Idempotency-Key`, exige os controlos administrativos existentes e alvo QA isolado. Não muda a API de parâmetros publicados para aceitar rascunhos. O snapshot congela regra, fonte/versão/hash, contexto declarado, data, passos racionais, decimal calculado, apresentação sem arredondamento e limitações. Alteração da regra não reescreve o histórico. Uma nova entrada produz nova execução; um snapshot fiscal não é convertido por edição numa simulação de planeamento.
## Adenda v1.1 — leitura dirigida de enquadramento e arredondamento
O cadastro persistido usa clone `simulator_fiscal_delta_20261021_test` e referência/actores sintéticos explicitamente identificados; não representa revisão jurídica por uma pessoa real nem atribuição persistente de papéis da Tempo. O recibo privado regista IDs, estado draft, zero regras publicadas e hash do relatório. A conta técnica e os ficheiros de prova privados não são material de deploy.
21/10/2026. Esta adenda substitui o estado de «não consultado» dos dispositivos abaixo, mantendo o histórico da v1 nas secções anteriores. Não atribui ao utilizador conferência local nem validação jurídica da ficha. Não altera a base de dados, o código, a governação ou o estado de publicação.
Planeamento personalizado mantém identificação própria. O resultado deste lote é apresentado como cálculo QA com regra em rascunho, não estimativa percentual introduzida pelo utilizador nem regra fiscal activada. As provas aceites de13f68f1b/valores/CSV/paginação não são repetidas. A entrega anterior permanece independente e concluída; não integra esta branch automaticamente.
### Exemplares efectivamente consultados
## Estimativa e revisão
- [CIVA republicado pela Lei13/2016, BR156, 10.º suplemento, 30/12/2016](source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf): cópia byte a byte do exemplar já inventariado acima, mesmo hash `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. Leitura visual dirigida das páginas6–8,13–14,17–18; folhas de contacto para localizar dispositivos. Não se afirma análise integral dos anexos.
- [BR16, I série, 16/04/2008, Decreto7/2008 e regulamento anexo](source-archive/BR-16-I-Serie-2008-04-16.pdf): recuperado do inventário existente, 28páginas, 2.267.865bytes, SHA256 `FC373FF5637EDF75EC6A9595DEC8763A8503D4EE786E3B7D8F1C742EAE9D80EE`. Texto pesquisável; página2 conferida visualmente para distinguir pagamento de liquidação. O BR contém também outros impostos: resultados de pesquisa fora do regulamento IVA não são fundamento deste lote.
- Republicação Lei22/2022 e Lei10/2025/BR248: reutilizados os exemplares acima; cotejo textual dos arts.15,17 e27, além da cadeia já analisada. Os sinais «[…]» preservam texto não alterado; não representam ausência de norma.
O exemplar de06/04/2017 localizado sob BR54 no inventário contém matéria aduaneira e não forneceu o Decreto8/2017 procurado. A coincidência de data não permite afirmar leitura dessa alteração do regulamento. Não foi localizado pelo nome/data, no inventário disponível, o texto do Decreto31/2012. Estes limites são de disponibilidade/correspondência nesta consulta, não afirmações de inexistência dos diplomas.
### Enquadramento documental confirmado e fronteira da conclusão
| Disposição exacta | Conteúdo confirmado por leitura | Aplicação ao percurso proposto |
|---|---|---|
| CIVA art.1(1)(a), PDF6, BR1426-(264) | Incidência sobre transmissões de bens e prestações de serviços onerosas no território nacional, nos termos do art.6, por sujeitos passivos nessa qualidade. | Não basta um montante: exigir natureza, onerosidade, localização e qualidade do sujeito. Importações do art.1(1)(b) ficam fora. |
| CIVA art.2(1)(a), PDF6 | Pessoas residentes ou com estabelecimento/representação e exercício independente habitual de actividades enumeradas. | Contexto do fornecedor tem de ser identificado. Não automatizar elegibilidade fiscal a partir do perfil de investidor. |
| CIVA arts.3(1),4(1), PDF6–7 | Transmissão geral: transferência onerosa de bens corpóreos; serviço: operação onerosa que não seja transmissão ou importação. | Classificar a operação; casos assimilados e especiais exigem enquadramento próprio e não entram implicitamente no lote. |
| CIVA art.6(1), PDF7, BR1426-(265) | Bens situados em território nacional no início do transporte/expedição ou, sem estes, quando postos à disposição. | Condição documental para transmissão interna ordinária. Serviços têm art.6(3) e excepções nos números seguintes; não declarar que a localização do prestador resolve todos os serviços. |
| CIVA art.15(1), PDF13, BR1426-(271) | Base geral é a contraprestação obtida ou a obter do adquirente, destinatário ou terceiro, sem prejuízo dos casos do n.º2. | Fundamenta base líquida tributável identificada, não qualquer receita prevista. Excluir do percurso automático as bases especiais do n.º2. |
| CIVA art.15(5)(a)/(b), PDF14, BR1426-(272) | Inclui outros impostos/direitos/taxas, excepto o próprio IVA, e despesas acessórias debitadas, incluindo comissões, embalagem, transporte e seguros por conta do cliente. | Base sem IVA pode incluir mais que o preço nominal. Entrada deve indicar composição. |
| CIVA art.15(6)(a)–(d), PDF14 | Exclui as indemnizações judiciais indicadas, descontos/abatimentos/bónus concedidos, pagamentos em nome/conta de terceiros registados adequadamente e embalagens retornáveis nas condições aí previstas. | Não excluir rubricas apenas pelo nome; conservar condições e evidência. O lote recebe base já determinada, sem construir um classificador destes casos. |
| CIVA art.17(2), PDF14 | Taxa aplicável é a vigente quando o imposto se torna exigível. | A data relevante não é automaticamente a criação da simulação. Ligar o resolvedor à data de exigibilidade identificada. |
| CIVA art.7(1)(a)/(b), PDF8, BR1426-(266) | Bens: colocação à disposição; serviços: realização ou cobrança/débito anterior nos termos do preceito. Números seguintes contêm situações próprias. | Não usar ano civil como substituto da data fiscal. Adiantamentos, prestações continuadas e outras excepções não são resolvidos por esta fórmula. |
| CIVA art.27(5)(c)/(d), PDF17, BR1426-(275) | Distingue preço líquido e restantes elementos da base da taxa e montante do imposto. Lei10/2025 mantém essas alíneas por «[…]», alterando o n.º10. | Sustenta separação entre base e IVA no resultado. Não fixa nestas alíneas modo/escala de arredondamento. |
| Regulamento, Decreto7/2008, arts.4 e5, PDF1–2 | Remete a matéria colectável para arts.15/16 do Código e o apuramento devido para arts.18–22. | IVA incidente da operação não equivale ao saldo a pagar após deduções. O lote não calcula declaração ou pagamento final. |
Período: Lei13/2016, art.4, PDF5, determina entrada em vigor em01/01/2017; taxa histórica de17% nesse exemplar não é usada para2026. A Lei22/2022 altera art.17(1) para16% desde01/01/2023 e adiciona art.15(10) para taxa reduzida, preservando os números gerais acima. A Lei10/2025, efectiva em01/01/2026, não lista art.15 ou17 entre os alterados. Esta cadeia suporta o percurso geral condicionado descrito; não certifica que todas as isenções, regimes e alterações regulamentares de2026 estejam consolidados. O intervalo de ensaio continua2026 e não cria expiração legal.
**Saldo do enquadramento:** fundamento geral da base e requisitos nucleares identificados. A elegibilidade de cada operação e as excepções não estão demonstradas por este facto. Para este lote manter entrada de base já apurada e contexto explícito, com sujeição geral comprovada ou declarada devidamente rotulada. Sem essa identificação, devolver indeterminação; não anunciar um classificador fiscal completo.
### Arredondamento: disposições encontradas que não podem ser generalizadas
| Disposição | Âmbito exacto | Conclusão |
|---|---|---|
| CIVA art.34, Lei10/2025, BR248/PDF14 | Extracção da base quando factura/registo já inclui imposto: dividir por116, multiplicar por100, arredondar para unidade mais próxima. | Não é arredondamento do IVA obtido de base líquida. Não transpor nem inferir empates. |
| Regulamento art.9(1)(b), Decreto7/2008, BR16/PDF2, p.100 | Valor do meio de pagamento quando usado cheque ou vale de correio: arredondamento por excesso para meticais. | Texto confirmado nesse exemplar histórico. Não é regra de cálculo por linha/factura nem prova de política geral para pagamento electrónico; vigência consolidada específica não fechada. Não aplicar ao imposto incidente do lote. |
Não foi encontrado, nos dispositivos efectivamente consultados, fundamento suficiente para fixar escala, modo, tratamento de empates e agregação do IVA calculado sobre base líquida. Isto **não prova inexistência** de instrução aplicável. Não adoptar convenções de software, ofícios portugueses ou páginas de calculadoras como norma moçambicana.
A [comunicação oficial do Governo de30/09/2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/) anuncia alteração dos arts.3,7,8,15,16-A,18,21,23,28,36 e37 do regulamento, referindo Decreto7/2008 e alterações31/2012 e8/2017. A comunicação não fornece o articulado publicado nem a eficácia de cada disposição. Serve para identificar a dependência concreta de consolidação; não substitui o decreto publicado nem permite presumir que afecte ou resolva o arredondamento do lote.
**Questão concreta para esclarecimento documental:** qual o diploma, despacho, instrução/circular da AT ou disposição aplicável em2026 que determina, para IVA incidente sobre base líquida em factura, (a) número de casas/unidade, (b) modo e empates, (c) arredondamento por linha, por taxa agrupada ou por documento e (d) relação com os totais declarados? Obter o dispositivo e período, incluindo alterações pertinentes do regulamento e instruções de facturação. Não é um pedido para escolher uma convenção técnica nem para reconfirmar autenticidade oficial. Nenhuma mensagem foi enviada a terceiros.
### Exemplos documentais condicionados
Pressupostos explícitos para os exemplos: operação interna onerosa à taxa geral, sujeito no regime aplicável, sem isenção/redução/base especial, exigibilidade em2026 e valores MZN. São exemplos aritméticos de aplicação dos preceitos, não operações reais classificadas ou casos finais de aceitação.
| Exemplo | Base nos termos do art.15 | IVA exacto a16% | Estado |
|---|---|---|---|
| Preço100 + transporte debitado10 − desconto concedido5; sem outras rubricas | 105, se verificadas as condições dos n.os5 e6 | 16,8 | Intermédio exacto; não chamar saldo a pagar. |
| Base já apurada100 | 100 | 16 | Exacto; não resolve política geral de arredondamento. |
| Base já apurada0,03 | 0,03 | 0,0048 | Valor fiscal apresentado após arredondamento indeterminado. |
| Duas linhas de base0,03 cada | 0,06 no total | 0,0048 por linha; 0,0096 no agregado | A convenção hipotética de arredondar ao cêntimo mais próximo produziria0,00 por linha e0,01 no agregado; diferença demonstra a dependência, não autoriza essa convenção. |
### Estimativa actualizada após a consulta
O enquadramento geral avançou; a dependência de arredondamento permanece concreta e a consolidação regulamentar pertinente não foi fechada. **Não é possível dar prazo incondicional de activação.** Estimativa restante de execução após disponibilização/esclarecimento desses elementos: **3–6h para concluir aplicação documental, ficha/casos e cadastro em rascunho QA; 12–20h para integração e aceitação técnica, total15–26h**. É reestimativa de engenharia, não horas medidas nem promessa de solução documental nesse intervalo. A espera por documento/esclarecimento e revisão/activação continuam excluídas. Se o fundamento introduzir agregação ou condições fora do contrato previsto, reestimar antes de ampliar o lote. Não houve implementação de código, cadastro ou activação nesta adenda.
As15–26h anteriores eram condicionais e incluíam o trabalho de cadastro e integração que agora avançou em QA. O esclarecimento de uma política genérica de arredondamento deixa de integrar o caminho crítico deste lote. O saldo deve usar os resultados técnicos registados na adenda do manual existente, distinguindo trabalho já executado, revisão e qualquer consumidor futuro de pagamento/declaração. Não são horas medidas, promessa de activação nem estimativa do simulador fiscal integral. Publicação, activação e integração em `main` continuam separadas e não foram executadas.
@@ -347,5 +347,47 @@
"rules_created": 0,
"rules_activated": 0
}
},
"fiscal_qa_v12_20261021": {
"instruction": "Tempo: exact decimal IVA calculation without automatic rounding; presentation distinct; no activation or main integration",
"supersedes": "v1.1 rounding-as-blocker and remaining estimate; no reopening accepted evidence",
"status": "QA scoped implementation verified; fiscal product remains partial AUD-SIM-INT02",
"proof": {
"synthetic_qa": true,
"database": "simulator_fiscal_delta_20261021_test",
"rule_id": "01a15d4e-c69b-7264-bdee-1e56ee88bf94",
"rule_status": "draft",
"registration_reused": true,
"simulation_id": "842e05f9-2b6e-4853-8ff5-81650633f6c4",
"report_id": "49cac0bd-f49c-4613-a8f0-1ee5c285cf81",
"exact_value": "0.0048",
"rounding_applied": false,
"report_sha256": "8ed8155a56fdf98ad26e2f01435644e062f27ca6b067ce3553415e974261c687",
"result_report_equal": true,
"previous_report_unchanged": true,
"published_rules": 0,
"source_reference_is_qa_fixture": true,
"human_legal_approval": false
},
"tests": {
"feature": 5,
"feature_assertions": 51,
"resolver_unit": 7,
"resolver_assertions": 25,
"frontend": 3,
"typescript": "passed",
"build": "passed",
"pint": "passed"
},
"browser_observation": "Actual component SSR and native report HTML fiscal delta observed in Edge; not a newly saved native PDF or complete browser history flow",
"limits": [
"synthetic source reference and QA actors; not human legal approval",
"QA PostgreSQL role bypasses RLS; application denials only",
"no payment or return balance; scope declared",
"no activation, migration, main merge or deployment"
],
"remaining_hours_scoped_qa_engineering": 0,
"previous_15_26_hours": "Historical conditional estimate includes work now performed, not measured hours; review/activation and fiscal product scope separate",
"record": "docs/investmoz/simulations/ficha-IVA-GERAL-BASE-LIQUIDA-v1.md"
}
}
@@ -1762,3 +1762,23 @@ A Tempo autorizou posteriormente a integração de `13f68f1b`. O commit foi inte
A [ficha documental IVA-GERAL-BASE-LIQUIDA v1](../simulations/ficha-IVA-GERAL-BASE-LIQUIDA-v1.md) desenvolve separadamente a proposta `0fafd348`: fundamento da taxa e período, limites do art.34, exemplar digitalizado recuperado do inventário, parâmetros por cadastrar, lacunas de enquadramento/arredondamento, integração e critérios de aceitação. É rascunho documental sem cadastro ou activação. AUD-SIM-INT02 mantém-se parcial; os outros sete saldos aceites não são reabertos. Não foram repetidas provas aceites nem alterados cálculos, dados ou regras. A branch fiscal permanece separada de `main`.
**Adenda fiscal v1.1 — 21/10/2026:** a mesma ficha regista a leitura visual dirigida do CIVA/Lei13/2016, arts.1–4,6–7,15,17 e27, e a consulta do regulamento/Decreto7/2008. Enquadramento geral da base documentado, com composição, exclusões, localização e data de exigibilidade; não constitui classificação automática de operações. O arredondamento do art.34 refere-se à extracção de base com IVA incluído e o art.9(1)(b) do regulamento ao meio de pagamento cheque/vale; nenhum foi convertido em regra geral de arredondamento sobre base líquida. Permanecem por esclarecer precisão/modo/empates/agregação e a correspondência regulamentar pertinente para2026. A ficha identifica a questão documental concreta e exemplos aritméticos, sem inventar norma. Estimativa restante condicional: cadastro/aplicação documental3–6h + código/aceitação12–20h =15–26h, excluindo espera, revisão e activação. Fontes locais arquivadas com hash coincidente; não se atribui conferência ao utilizador. Nenhuma regra activada; AUD-SIM-INT02 continua parcial e a entrega13f68f1b mantém-se independente.
### Decisão posterior da Tempo e execução fiscal QA v1.2 — 21/10/2026
**Estado actual; substitui a condição bloqueante e a estimativa da adenda v1.1.** A Tempo determinou prosseguir neste lote sem arredondamento automático, preservando precisão decimal e distinguindo valor calculado de apresentação monetária. A ausência de uma política genérica de arredondamento deixa de bloquear o cálculo do IVA sobre base líquida. Isto não é regra universal para todos os impostos. Exigências de precisão de um formato de pagamento/declaração serão identificadas nesse consumidor, sem as generalizar. A ficha existente foi consolidada emv1.2; versões anteriores permanecem emGit. Não há questão adicional à Tempo como pré-condição deste cálculo nem validação jurídica atribuída ao utilizador.
**Cadastro:** artefacto `database/qa/iva-general-net-v1.json`; comando explícito `fiscal:qa-register-iva {actor} {source}`, com guarda da base QA isolada, papel existente de editor e vínculo a fonte cadastrada. Confere SHA256 do exemplar e usa a governação existente para criar apenas `draft`. Repetição conserva o mesmoID/versão; divergência não sobrescreve o cadastro. Não corre em deploy, importação ou migration. O clone persistido é `simulator_fiscal_delta_20261021_test`,127.0.0.1:55441. A referência de fonte e os actores usados no ensaio são fixtures sintéticas identificadas; não constituem revisão jurídica por pessoa real nem atribuição persistente de papéis organizacionais.
**Cálculo e proveniência:** `SimulationFiscalQaPreview` usa o resolvedor temporal e avaliador existentes, com percurso explícito de rascunho QA que não altera a elegibilidade da API de parâmetros publicados. O resultado guarda contexto declarado, fonte/versão/hash, regra/versão/DSL, passos racionais e decimal exacto; apresentação guarda valor e moeda separadamente, com `rounding_applied=false`. Não há float, truncagem, `toFixed` ou nó`round` neste percurso. Exemplos dirigidos:0,03×16%=0,0048;9007199254740993,0003×16%=1441151880758558,880048; zero explícito conserva0. Fonte/versão incompatível e fora da cobertura2026 continuam indeterminados, sem taxa substituta/zero. A data declarada de exigibilidade alimenta a resolução, sem expiração legal artificial em31/12/2026.
**Histórico e relatórios:** armazenamento existente de simulações, fontes de resultado e relatórios; sem schema novo. Entradas declaradas e proveniência da regra são fontes separadas. Snapshot/relatório não são reavaliados quando a regra é editada. Nova execução usa novo registo; a mesma chave e parâmetros reutiliza a execução. O ensaio persistido actual tem simulação`842e05f9-2b6e-4853-8ff5-81650633f6c4`, relatório`49cac0bd-f49c-4613-a8f0-1ee5c285cf81`, hash`8ed8155a56fdf98ad26e2f01435644e062f27ca6b067ce3553415e974261c687`; resultado/relatório iguais e relatório anterior conservado. Zero regras publicadas. Ecrã e linhas do dossier/CSV distinguem cálculo exacto QA, contexto declarado, estimativas do utilizador e saldo a pagar, que não é calculado neste lote.
**Verificação deste incremento:**5testes feature/51asserções;7testes do resolvedor/25asserções;3testes frontend;TypeScript,build ePint aprovados. Os testes feature correm com transacções no clone dedicado `simulator_fiscal_checks_20261021_test`, separado do cadastro persistido. A primeira tentativa de base vazia encontrou uma migration antiga que exige instalação nominal; não se contornou essa guarda nem se alterou o activo: usou-se cópia do schema QA existente e transacções, sem novas migrations. Cobertura inclui não publicação, indeterminação efectiva, idempotência, precisão fraccionária/grande, moeda obrigatória, recusa de nó de arredondamento, recusa foraQA, snapshot após edição, recuperação por nova autenticação API e recusa por outra conta.
Conferência visual dirigida no Edge do componente React real renderizado emSSR e do HTML produzido por `createProjectDossierPrintHtml` apenas para as novas linhas fiscais:0.0048, base0.03, taxa16, MZN e rótulos legíveis, sem corte visível. O invólucro de inspecção suprimiu apenas a abertura automática da janela de impressão numa cópia privada do HTML; não alterou o exportador. Não é prova de novo PDF nativo guardado nem novo percurso browser de histórico no formulário completo. API/histórico/relatório imutável e os modelos usados pelo ecrã/CSV/dossier têm provas dirigidas. A conta técnica QA ultrapassaRLS; recusas demonstram política da aplicação, não nova certificaçãoRLS. Provas aceites das10páginas, valores, CSV e recuperação/exportações de13f68f1b reutilizadas, sem repetição.
Provas privadas em`outputs/simulator-continuation-20261020/`: `fiscal-qa-receipt-v2.json`, `fiscal-qa-tests.json`, `fiscal-qa-build.log`, `fiscal-qa-snapshot.json`, `fiscal-component-review.html` e `fiscal-report-review.html`. Página/servidor temporários de inspecção encerrados. O recibo versionado desta continuação recebe a mesma reconciliação; a matriz existente recebe apenas adenda emAUD-SIM-INT02. Não se cria backlog paralelo.
**Saldo e estimativa:** o recorte agora autorizado — cadastro versionado, precisão/proveniência, resolução temporal QA, histórico e dados de relatório — está implementado e verificado nos limites acima;0h de engenharia identificada restante nesse recorte, revisão não incluída. As15–26h eram estimativa condicional anterior que incluía trabalho agora executado, não horas medidas nem estimativa ainda integralmente por consumir. AUD-SIM-INT02 permanece parcial para disponibilização fiscal de produto e cobertura integral; os outros sete itens não são reabertos. Pagamento, declaração, classificação automática e outros impostos exigem âmbito/estimativa próprios. Não declarar o simulador fiscal concluído.
**Integração:** commit desta candidata fica apenas na branch `codex/simulator-integrated-review`, descendente de0fafd348, sem push/merge emmain. A autorização anterior de integração de13f68f1b não é reutilizada para esta candidata fiscal. Após revisão consolidada, uma decisão específica de integração continua separada de cadastro/publicação de regras. Esta preparação não muda o destino, papéis activos ou dados activos. Não retirar a guarda QA ou mudar o ambiente do activo para contornar a separação; preparar o consumidor de produto e a sua disponibilização apenas no âmbito que vier a ser autorizado.
@@ -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,QA zero rules verified with full visibility; prepared archive and analyses reused; current homologation read awaits SSH authentication,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [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.",,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,QA zero rules verified with full visibility; prepared archive and analyses reused; current homologation read awaits SSH authentication,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [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.",,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,,,,,,,,,,,,,,
Can't render this file because it is too large.
@@ -0,0 +1,24 @@
import { fiscalQaRows } from "@/lib/fiscal-qa-preview";
export default function FiscalQaPreview({ snapshot }: { snapshot: unknown }) {
const rows = fiscalQaRows(snapshot);
if (rows.length === 0) return null;
return (
<section
aria-label="Pré-visualização fiscal QA"
className="my-3 rounded border border-amber-700 bg-amber-50 p-3"
>
<h5 className="font-semibold">
Pré-visualização fiscal QA — cálculo exacto sem arredondamento
</h5>
<dl className="mt-2 space-y-2 text-sm">
{rows.map(([label, value]) => (
<div key={label}>
<dt className="font-medium">{label}</dt>
<dd className="break-all">{value}</dd>
</div>
))}
</dl>
</section>
);
}
+7 -5
View File
@@ -4,6 +4,7 @@ import {
} from '@/components/public-document-content';
import { DossierPrefill } from '@/components/dossier-prefill';
import { createOperationKey } from '@/lib/operation-key';
import FiscalQaPreview from './fiscal-qa-preview';
import { displayValue } from '@/lib/display-value';
import {
useCallback,
@@ -886,6 +887,7 @@ export function SimulationForm() {
() => readVersionedArithmeticReportReference(result),
[result],
);
const isFiscalQaPreview = arithmeticSnapshot?.scenario_kind === 'fiscal_rule_qa_preview';
const versionedReportMissing =
arithmeticResult.integrity === 'versioned' &&
arithmeticReportReference === null;
@@ -3705,7 +3707,7 @@ export function SimulationForm() {
{(current.id === 'tax' || result) && (
<div className="space-y-3 border-t pt-4">
<h4 className="font-semibold">
{current.id === 'tax'
{isFiscalQaPreview ? 'Pré-visualização fiscal QA seleccionada' : current.id === 'tax'
? 'Cenário personalizado de planeamento'
: 'Resultado histórico seleccionado'}
</h4>
@@ -3769,7 +3771,7 @@ export function SimulationForm() {
submetido.
</p>
)}
{result.status === 'completed' && (
{result.status === 'completed' && !isFiscalQaPreview && (
<p>
Resultado aritmético indicativo:{' '}
<strong className="break-all">
@@ -3806,9 +3808,10 @@ export function SimulationForm() {
taxas legais, elegibilidade ou obrigação
fiscal.
</p>
{isFiscalQaPreview && <FiscalQaPreview snapshot={result.result_snapshot} />}
{result.status === 'completed' && (
<div className="mt-3 space-y-2 border-t pt-3">
{arithmeticResult.integrity !==
{!isFiscalQaPreview && arithmeticResult.integrity !==
'invalid' && (
<details className="rounded border p-3">
<summary className="cursor-pointer font-semibold focus-visible:outline-2">
@@ -4179,8 +4182,7 @@ export function SimulationForm() {
'não identificada',
'não identificada',
)}{' '}
· resultado
aritmético{' '}
· {source.result_snapshot.scenario_kind === 'fiscal_rule_qa_preview' ? 'IVA calculado exacto em QA' : 'resultado aritmético'}{' '}
{displayScalar(
sourceResult.value ??
(sourceResult.integrity ===
+59
View File
@@ -0,0 +1,59 @@
import { isApiSimulationResultSnapshotV1 } from "./simulation-contract.ts";
export function fiscalQaRows(value: unknown): Array<[string, string]> {
if (!isApiSimulationResultSnapshotV1(value)) return [];
const snapshot = value as unknown as Record<string, unknown>;
if (snapshot.scenario_kind !== "fiscal_rule_qa_preview") return [];
const provenance = snapshot.fiscal_provenance as
Record<string, unknown> | undefined;
const rule = provenance?.rule as Record<string, unknown> | undefined;
const source = rule?.source as Record<string, unknown> | undefined;
const parameters = rule?.parameters as
Record<string, Record<string, unknown>> | undefined;
const intermediate = snapshot.intermediate as Record<
string,
unknown
> | null;
const text = (item: unknown) =>
typeof item === "string" || typeof item === "number"
? String(item)
: "não disponível";
return [
["Relatório", "Pré-visualização fiscal QA de rascunho — sem activação"],
[
"IVA calculado (exacto; não é saldo a pagar)",
value.result.value ?? "Indeterminado",
],
["Explicação", value.result.explanation],
["Base tributável declarada", text(snapshot.base)],
["Taxa cadastrada (%)", text(parameters?.IVA_GENERAL_RATE?.value)],
["Moeda explícita", text(value.context.currency)],
["Data de exigibilidade declarada", value.context.reference_date],
[
"Enquadramento",
"Declarado pelo utilizador; não validado juridicamente",
],
[
"Regra / versão / estado",
`${text(rule?.rule_code)} / ${text(rule?.rule_version)} / rascunho`,
],
[
"Vigência cadastrada",
`${text(rule?.valid_from)} até ${rule?.valid_until === null ? "sem termo indicado" : text(rule?.valid_until)}`,
],
[
"Fonte da taxa",
`${text(source?.diploma)} — ${text(source?.article)}`,
],
["Hash do exemplar da taxa", text(rule?.source_sha256)],
["Representação racional exacta", text(intermediate?.result)],
[
"Arredondamento",
"Não aplicado. Valor calculado preservado sem truncar ou arredondar; apresentação monetária separada.",
],
[
"Limitações",
"Regra não publicada; cobertura demonstrada em 2026; sem deduções, declaração ou saldo a pagar.",
],
];
}
+10 -2
View File
@@ -1,4 +1,5 @@
import { displayValue } from './display-value.ts';
import { fiscalQaRows } from './fiscal-qa-preview.ts';
import {
buildProjectDossierRows,
type ProjectDossierRow,
@@ -344,6 +345,11 @@ function makeSections(
}
const finance = sections.get('finance');
if (finance && currentCalculation?.snapshot?.scenario_kind === 'fiscal_rule_qa_preview') {
for (const [field, value] of fiscalQaRows(currentCalculation.snapshot)) {
finance.rows.push({ field, value, origin: 'Snapshot fiscal QA preservado; sem recálculo', classification: 'Rascunho QA; cálculo exacto sem arredondamento automático; não é saldo a pagar' });
}
}
if (finance && currentCalculation) {
const snapshot = currentCalculation.snapshot;
const result =
@@ -417,10 +423,12 @@ function makeSections(
{
field: 'Fórmula e valores usados',
value:
snapshot && typeof snapshot.formula_version === 'string'
snapshot?.scenario_kind === 'fiscal_rule_qa_preview'
? `Base tributável declarada × taxa cadastrada ÷ 100; cálculo decimal exacto, sem arredondamento. Ver regra e proveniência QA acima.`
: snapshot && typeof snapshot.formula_version === 'string'
? `Fórmula ${snapshot.formula_version}; base ${displayValue(snapshot.base, 'não disponível')}; taxa ${displayValue(snapshot.rate_percent, 'não disponível')}%; redução ${displayValue(snapshot.reduction_percent, 'não disponível')}%; moeda ${displayValue(snapshot.currency, 'não indicada')}`
: 'A fórmula não foi identificada no snapshot; ver explicação da execução',
origin: 'Snapshot guardado; sem aplicação de regra legal',
origin: snapshot?.scenario_kind === 'fiscal_rule_qa_preview' ? 'Snapshot QA de regra em rascunho; sem activação ou validação jurídica da operação' : 'Snapshot guardado; sem aplicação de regra legal',
classification: 'Reutilização do resultado existente',
},
{
+8
View File
@@ -1,4 +1,5 @@
import { csvCell } from './csv.js';
import { fiscalQaRows } from './fiscal-qa-preview.ts';
import { isApiSimulationResultSnapshotV1 } from './simulation-contract.ts';
export type SimulationDraft = Record<string, string | string[]>;
@@ -1848,6 +1849,13 @@ export function exportCompletedArithmeticCsv(
);
}
const result = simulation.result_snapshot as Record<string, unknown>;
if (result.scenario_kind === 'fiscal_rule_qa_preview') {
const rows = fiscalQaRows(result);
const report = readVersionedArithmeticReportReference(simulation);
if (!rows.length || !report) throw new Error('A pré-visualização fiscal não corresponde ao relatório preservado.');
return `\uFEFF${[...rows, ['ID do relatório', report.id], ['Hash do relatório', report.report_sha256], ['Data de exportação', exportedAt]]
.map((field) => field.map(csvCell).join(',')).join('\r\n')}`;
}
const decimal = (value: unknown, label: string) => {
if (typeof value !== 'string' || !/^(?:\d+)(?:\.\d+)?$/.test(value))
throw new Error(
+1
View File
@@ -27,6 +27,7 @@ Route::middleware([CorrelationId::class, 'auth', 'verified', \App\Http\Middlewar
Route::post('/api/v2/investment/admin/tax-rules', [SimulationTaxRuleGovernanceController::class, 'store']);
Route::get('/api/v2/investment/admin/tax-rules/{id}', [SimulationTaxRuleGovernanceController::class, 'show'])->whereUuid('id');
Route::put('/api/v2/investment/admin/tax-rules/{id}', [SimulationTaxRuleGovernanceController::class, 'update'])->whereUuid('id');
Route::post('/api/v2/investment/admin/tax-rules/{id}/qa-preview', [SimulationTaxRuleGovernanceController::class, 'qaPreview'])->whereUuid('id');
Route::post('/api/v2/investment/admin/tax-rules/{id}/{action}', [SimulationTaxRuleGovernanceController::class, 'transition'])
->whereUuid('id')->whereIn('action', ['submit', 'review', 'reject', 'approve', 'publish', 'suspend', 'archive']);
Route::get('/management/investment/zones/operational-install',function(\Illuminate\Http\Request $r,\App\Services\InvestmentZoneOperationalDetailsInstallation $s){return response()->json(['plan'=>$s->preview($r->user())]);});
@@ -0,0 +1,150 @@
<?php
namespace Tests\Feature;
use App\Jobs\RunSimulation;
use App\Models\InvestmentLegalSource;
use App\Models\Simulation;
use App\Models\SimulationTaxRuleVersion;
use App\Models\User;
use App\Services\SimulationExecution;
use App\Services\SimulationTaxParameterResolver;
use App\Services\SimulationTaxRuleGovernance;
use Brick\Math\BigRational;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use Tests\TestCase;
class SimulationFiscalQaPreviewTest extends TestCase
{
use DatabaseTransactions;
private User $editor;
private InvestmentLegalSource $source;
private string $ruleId;
protected function setUp(): void
{
parent::setUp();
Queue::fake();
$this->editor = User::factory()->withTwoFactor()->create();
config(['portal.managers' => [$this->editor->email], 'simulation-tax-governance.editors' => [$this->editor->email],
'simulation-tax-governance.publishers' => [$this->editor->email]]);
$this->source = InvestmentLegalSource::create([
'title' => 'QA fixture: Lei22/2022 republicada; sem activação',
'url' => 'https://www.inm.gov.mz/', 'diploma' => 'Lei 22/2022', 'article' => 'Artigo 17(1)',
'competent_entity' => 'Assembleia da República', 'valid_from' => '2023-01-01', 'valid_until' => null,
'validation_status' => 'validated', 'owner_id' => $this->editor->id, 'version_no' => 1,
'validator_id' => User::factory()->create()->id, 'validated_at' => now(),
]);
$this->artisan('fiscal:qa-register-iva', ['actor' => $this->editor->id, 'source' => $this->source->id])->assertSuccessful();
$this->ruleId = SimulationTaxRuleVersion::where('rule_code', 'IVA-GERAL-BASE-LIQUIDA')->value('id');
}
private function preview(array $overrides = [], ?string $key = null)
{
return $this->actingAs($this->editor)->postJson('/api/v2/investment/admin/tax-rules/'.$this->ruleId.'/qa-preview', [
'base' => '0.03', 'currency' => 'MZN', 'reference_date' => '2026-10-21', 'operation' => 'internal_general_net', ...$overrides,
], ['Idempotency-Key' => $key ?? (string) Str::uuid()]);
}
private function finish(string $id): array
{
(new RunSimulation($id, $this->editor->id, null))->handle(app(SimulationExecution::class));
return Simulation::findOrFail($id)->result_snapshot;
}
public function test_draft_registration_reuses_version_and_never_exposes_a_published_parameter(): void
{
$this->artisan('fiscal:qa-register-iva', ['actor' => $this->editor->id, 'source' => $this->source->id])->assertSuccessful();
$this->assertSame(1, SimulationTaxRuleVersion::where('rule_code', 'IVA-GERAL-BASE-LIQUIDA')->count());
$this->assertDatabaseHas('simulation_tax_rule_versions', ['id' => $this->ruleId, 'status' => 'draft']);
$resolved = app(SimulationTaxParameterResolver::class)->resolvePublishedForPeriod('IVA', '2026-10-21', '2026-10-21',
['operation' => 'internal_general_net', 'currency' => 'MZN']);
$this->assertSame([], $resolved);
$this->actingAs($this->editor)->postJson('/api/v2/investment/admin/tax-rules/'.$this->ruleId.'/publish', ['version_no' => 1, 'evidence' => 'QA attempt must not publish an incomplete fiscal rule.'])->assertUnprocessable();
}
public function test_fractional_result_is_exact_and_snapshot_survives_rule_edits_and_new_session(): void
{
$key = (string) Str::uuid();
$id = $this->preview([], $key)->assertAccepted()->json('data.id');
$this->preview([], $key)->assertOk()->assertJsonPath('replayed', true)->assertJsonPath('data.id', $id);
$this->preview(['base' => '1'], $key)->assertConflict();
$snapshot = $this->finish($id);
$this->assertSame('0.0048', $snapshot['result']['value']);
$this->assertSame('calculated', $snapshot['result']['status']);
$this->assertSame('0.0048', $snapshot['presentation']['value']);
$this->assertFalse($snapshot['presentation']['rounding_applied']);
$this->assertEquals(0, BigRational::of($snapshot['intermediate']['result'])->compareTo('0.0048'));
$this->assertSame('draft', $snapshot['fiscal_provenance']['rule']['status']);
$this->assertSame('user_declared', $snapshot['fiscal_provenance']['scope_status']);
$this->assertSame([], $snapshot['result']['missing_inputs']);
$rule = SimulationTaxRuleVersion::findOrFail($this->ruleId);
$params = $rule->parameters;
$params['IVA_GENERAL_RATE']['value'] = '17';
app(SimulationTaxRuleGovernance::class)->update($this->editor, $this->ruleId, 1, [
'rule_code' => $rule->rule_code, 'rule_version' => 1, 'component' => 'IVA', 'scope_key' => $rule->scope_key,
'valid_from' => '2023-01-01', 'valid_until' => null, 'legal_source_id' => $this->source->id,
'source_sha256' => $rule->source_sha256, 'rule_definition' => $rule->rule_definition, 'parameters' => $params,
'rounding_policy' => $rule->rounding_policy,
]);
auth()->logout();
$this->actingAs(User::findOrFail($this->editor->id))->getJson('/api/v1/simulations/'.$id)->assertOk()
->assertJsonPath('data.result_snapshot.fiscal_provenance.rule.parameters.IVA_GENERAL_RATE.value', '16');
$reportId = DB::table('simulation_reports')->where('simulation_id', $id)->value('id');
$this->getJson('/api/v1/simulations/'.$id.'/reports/'.$reportId)->assertOk()->assertJsonPath('data.report_snapshot', $snapshot);
$this->getJson('/api/v1/simulations/history')->assertOk()->assertJsonPath('meta.total', 1);
$this->assertDatabaseHas('simulation_result_sources', ['source_type' => 'tax_rule_draft', 'source_id' => $this->ruleId]);
$resultId = DB::table('simulation_results')->where('simulation_id', $id)->value('id');
$declared = json_decode(DB::table('simulation_result_sources')->where('result_id', $resultId)->where('source_type', 'user_input')->value('source_snapshot'), true);
$this->assertSame('0.03', $declared['base']);
$this->assertArrayNotHasKey('fiscal_preview', $declared);
$other = User::factory()->create(['email_verified_at' => now()]);
$this->actingAs($other)->getJson('/api/v1/simulations/'.$id)->assertNotFound();
$this->getJson('/api/v1/simulations/'.$id.'/reports/'.$reportId)->assertNotFound();
$this->getJson('/api/v1/simulations/history')->assertOk()->assertJsonPath('meta.total', 0);
}
public function test_coverage_and_source_version_mismatch_stay_indeterminate_without_an_intermediate(): void
{
$id = $this->preview(['reference_date' => '2025-12-31'])->assertAccepted()->json('data.id');
$snapshot = $this->finish($id);
$this->assertContains('demonstrated_coverage', $snapshot['result']['missing_inputs']);
$this->assertNull($snapshot['intermediate']);
$this->source->update(['version_no' => 2]);
$id = $this->preview()->assertAccepted()->json('data.id');
$snapshot = $this->finish($id);
$this->assertContains('eligible_parameter', $snapshot['result']['missing_inputs']);
$this->assertNull($snapshot['intermediate']);
}
public function test_zero_is_an_explicit_input_missing_currency_is_rejected_and_non_qa_preview_is_unavailable(): void
{
$id = $this->preview(['base' => '0'])->assertAccepted()->json('data.id');
$snapshot = $this->finish($id);
$this->assertEquals(0, BigRational::of($snapshot['intermediate']['result'])->compareTo(0));
$this->assertSame('0', $snapshot['result']['value']);
$largeId = $this->preview(['base' => '9007199254740993.0003'])->assertAccepted()->json('data.id');
$this->assertSame('1441151880758558.880048', $this->finish($largeId)['result']['value']);
$this->preview(['currency' => null])->assertUnprocessable();
config(['database.connections.pgsql.url' => 'not-an-isolated-qa-target']);
$this->preview()->assertNotFound();
}
public function test_this_consumer_refuses_a_round_node_instead_of_silently_applying_it(): void
{
$rule = SimulationTaxRuleVersion::findOrFail($this->ruleId);
$definition = $rule->rule_definition;
$definition['nodes']['rounded'] = ['op' => 'round', 'args' => ['intermediate'], 'scale' => 2, 'mode' => 'HALF_UP'];
$definition['result'] = 'rounded';
// Deliberately altered QA draft; no rule is published by this test.
$rule->update(['rule_definition' => $definition]);
$this->preview()->assertUnprocessable()->assertJsonPath('message', 'Este percurso preserva o valor exacto e não aplica arredondamento automático.');
}
}
+133
View File
@@ -0,0 +1,133 @@
import assert from "node:assert/strict";
import test from "node:test";
import { fiscalQaRows } from "../../resources/js/lib/fiscal-qa-preview.ts";
import { exportCompletedArithmeticCsv } from "../../resources/js/lib/simulation-form.ts";
import { createProjectDossierReport } from "../../resources/js/lib/project-dossier-report.ts";
function fixture() {
const snapshot = {
contract_version: 1,
scenario_kind: "fiscal_rule_qa_preview",
base: "0.03",
currency: "MZN",
indicative_total: "0.0048",
legal_interpretation: false,
formula_version: "investmoz.tax.v1",
context: {
reference_date: "2026-10-21",
currency: "MZN",
currency_fraction_digits: 2,
scenario_id: "61000100-0000-4000-8000-000000000099",
scenario_version: 1,
engine_version: "fiscal-draft-preview-v1",
ruleset_version: "IVA-GERAL-BASE-LIQUIDA:1",
},
result: {
status: "calculated",
value: "0.0048",
unit: "MZN",
explanation:
"Cálculo exacto QA; enquadramento declarado; não é saldo a pagar.",
missing_inputs: [],
source_ids: ["source-fixture"],
},
intermediate: { result: "48/10000" },
fiscal_provenance: {
rule: {
rule_code: "IVA-GERAL-BASE-LIQUIDA",
rule_version: 1,
valid_from: "2023-01-01",
valid_until: null,
source_sha256: "a".repeat(64),
source: { diploma: "Lei 22/2022", article: "Artigo 17(1)" },
},
},
};
return {
id: "61000100-0000-4000-8000-000000000088",
status: "completed",
version_no: 2,
result_snapshot: snapshot,
reports: [
{
id: "61000100-0000-4000-8000-000000000077",
version_no: 1,
status: "ready",
report_snapshot: structuredClone(snapshot),
report_sha256: "b".repeat(64),
},
],
};
}
test("screen rows preserve sub-cent IVA as a string and distinguish calculation from payment", () => {
const simulation = fixture();
const before = JSON.stringify(simulation);
const rows = fiscalQaRows(simulation.result_snapshot);
assert.equal(
rows.find(([label]) => label.startsWith("IVA calculado"))[1],
"0.0048",
);
assert.match(
rows.find(([label]) => label === "Arredondamento")[1],
/Não aplicado/,
);
assert.match(
rows.find(([label]) => label === "Enquadramento")[1],
/não validado juridicamente/,
);
assert.equal(JSON.stringify(simulation), before);
});
test("CSV preserves the exact amount and refuses a mismatched immutable report", () => {
const simulation = fixture();
const csv = exportCompletedArithmeticCsv(
simulation,
"2026-10-21T09:00:00Z",
);
assert.match(csv, /0\.0048/);
assert.doesNotMatch(csv, /Resultado fiscal final,Indeterminado/);
simulation.reports[0].report_snapshot.result.value = "0.00";
assert.throws(
() => exportCompletedArithmeticCsv(simulation),
/relatório preservado/,
);
});
test("dossier report uses the same exact snapshot and preserves genuine indetermination", () => {
const simulation = fixture();
function report() {
return createProjectDossierReport(
{ project_title: "QA fiscal", currency: "MZN" },
{
selectedSections: new Set(["finance"]),
currentCalculation: {
status: "completed",
version: 2,
snapshot: simulation.result_snapshot,
},
},
);
}
const rows = report().sections.find(
(section) => section.id === "finance",
).rows;
assert.equal(
rows.find((row) => row.field.startsWith("IVA calculado")).value,
"0.0048",
);
assert.equal(
rows.find((row) => row.field === "Fonte da taxa").value,
"Lei 22/2022 — Artigo 17(1)",
);
simulation.result_snapshot.result.status = "indeterminate";
simulation.result_snapshot.result.value = null;
simulation.result_snapshot.indicative_total = null;
simulation.result_snapshot.result.missing_inputs = ["eligible_parameter"];
assert.equal(
report()
.sections.find((section) => section.id === "finance")
.rows.find((row) => row.field.startsWith("IVA calculado")).value,
"Indeterminado",
);
});