aeat.domain.renta._maritime_exemption module

Maritime worker IRPF exemption calculation engine.

Implements the three legally distinct exemption pathways for trabajadores del mar and the associated profile completeness gate. Each calculation function returns a CasillaObservation carrying the exempt amount and its full legal provenance.

Active exemption axes (2024/2025):

Art. 7.p) LIRPF (Ley 35/2006, BOE-A-2006-20764)

Conditions: foreign-flagged vessel OR international waters; foreign entity receiving services; territory with CDI or equivalent income tax. Annual cap: 60,100 EUR. Formula: min(annual_salary / 365 * qualifying_days, 60_100)

REBECA 50% exemption (Ley 19/1994, Arts. 73.2 73.3 75.1 75.3, BOE-A-1994-15794)

Crew of REBECA-registered vessels or scheduled Canary Islands routes. Exempt 50% is excluded from the Modelo 111 withholding base by employer.

Inactive axis (future variant):

DA 41 LIRPF (Ley 35/2006 DA 41, added by Ley 26/2014 BOE-A-2014-12327)

50% exemption for tuna fleet crew. Requires EU state-aid clearance not granted as of 2024/2025. Engine raises MaritimeExemptionInactiveError when the selector resolves True so it is never silently applied.

Profile completeness gate (not a calculation axis):

RETM mandatory filing (Ley 35/2006 Art. 96, BOE-A-2006-20764)

Since January 2023 all RETMAR-registered workers must file IRPF regardless of income level. Raises ProfileCompletenessError; it does not alter casilla values.

Provisions outside scope:

The transitional withholding rule from January 2015 (a different disposicion adicional with no maritime content) must not be cited as a maritime exemption anchor in any registry or code artefact. Art. 17.1.d) / Art. 9 RIRPF daily allowance caps apply generically; TEAC narrowed their scope for crew whose ordinary workplace is the vessel (STS 954/2020, STS 3185/2021). No special per-diem exists in any currently applicable LIRPF provision for these workers.

exception MaritimeExemptionInactiveError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: RentaError

Raised when the DA 41 selector resolves True for a trabajador del mar.

DA 41 LIRPF requires prior EU state-aid clearance under TFEU rules. That clearance has not been granted as of 2024/2025. The engine raises this error instead of silently producing an exempt-income observation, which would be legally incorrect output.

Once EU clearance is granted a follow-up task must flip the binding status in trabajador_del_mar.toml and add oracle-backed tests; no code change in this module is required.

Legal authority: Ley 35/2006 DA 41 BOE-A-2006-20764 (as amended by Ley 26/2014 BOE-A-2014-12327).

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception ProfileCompletenessError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: RentaError

Raised when a RETMAR-registered worker’s profile is presented for filing.

Since 2023, all workers registered in the maritime special Social Security regime must file an IRPF declaration regardless of income level (Ley 35/2006 art. 96, BOE-A-2006-20764). This is a profile completeness gate — it does not alter casilla values or formula execution paths.

Callers should surface the mandatory-filing status to the operator in CLI JSON output; the warning must not suppress further processing.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
class MaritimeWorkerFacts(worker_class=None, vessel_flag=None, waters_type=None, vessel_registry=None, tuna_fleet=False, pending_eu_clearance=False, retmar_registered=False)[source]

Bases: object

Resolved profile facts that gate maritime exemption pathway selection.

All fields are optional and default to the non-triggering value so that callers building partial profiles do not require every fact to be present. A profile without worker_class = “trabajador_del_mar” is unaffected by any predicate in this module.

Variables:
  • worker_class – Must be "trabajador_del_mar" to activate any maritime exemption pathway.

  • vessel_flag"ES" (Spanish-flagged) or "foreign".

  • waters_type"national" or "international".

  • vessel_registry – One of "REBECA", "rebeca_eu_eea", "scheduled_canary_route", or None.

  • tuna_fleet – Whether the vessel is a qualifying tuna fleet vessel (DA 41 selector; currently always inactive).

  • pending_eu_clearance – Mirrors the DA 41 TOML selector field. Must be True alongside tuna_fleet to trigger the inactive gate.

  • retmar_registered – Whether the taxpayer is in the RETMAR register. Drives the mandatory-filing completeness check only.

Parameters:
  • worker_class (str | None)

  • vessel_flag (Literal['ES', 'foreign'] | None)

  • waters_type (Literal['national', 'international'] | None)

  • vessel_registry (Literal['REBECA', 'rebeca_eu_eea', 'scheduled_canary_route'] | None)

  • tuna_fleet (bool)

  • pending_eu_clearance (bool)

  • retmar_registered (bool)

worker_class: str | None
vessel_flag: Optional[Literal['ES', 'foreign']]
waters_type: Optional[Literal['national', 'international']]
vessel_registry: Optional[Literal['REBECA', 'rebeca_eu_eea', 'scheduled_canary_route']]
tuna_fleet: bool
pending_eu_clearance: bool
retmar_registered: bool
art_7p_eligible(facts)[source]

Return True when Art. 7.p) LIRPF applies to the worker profile.

Conditions (conjunctive): - worker_class == “trabajador_del_mar” - vessel_flag == “foreign” OR waters_type == “international”

The ADR notes that international waters qualify for foreign-flagged vessels per AEAT accepted practice, confirmed by TEAR Galicia December 2024 for Galician fishing crew and by Supreme Court doctrine extended April 2025 to military Navy in NATO/UN sea operations.

Legal authority: Ley 35/2006 Art. 7.p) BOE-A-2006-20764.

Return type:

bool

Parameters:

facts (MaritimeWorkerFacts)

rebeca_eligible(facts)[source]

Return True when the REBECA 50% exemption applies to the worker profile.

Conditions: - worker_class == “trabajador_del_mar” - vessel_registry in {“REBECA”, “rebeca_eu_eea”, “scheduled_canary_route”}

The “rebeca_eu_eea” value covers the 2021 extension to crews of REBECA-registered company vessels enrolled in other EU/EEA member state registries (Ley 19/1994 Art. 75.1).

Legal authority: Ley 19/1994 Arts. 73.2 73.3 75.1 75.3 BOE-A-1994-15794.

Return type:

bool

Parameters:

facts (MaritimeWorkerFacts)

da41_eligible(facts)[source]

Return True when the DA 41 tuna-fleet selector resolves.

DA 41 requires: - worker_class == “trabajador_del_mar” - tuna_fleet == True - pending_eu_clearance == True

The binding is currently inactive_pending_eu_clearance. Callers must check the return value and raise MaritimeExemptionInactiveError when True; this predicate is deliberately separated from the error-raising path so it can be tested independently.

Legal authority: Ley 35/2006 DA 41 BOE-A-2006-20764 (as amended by Ley 26/2014 BOE-A-2014-12327).

Return type:

bool

Parameters:

facts (MaritimeWorkerFacts)

calculate_art_7p_exemption(*, annual_salary, qualifying_days, facts)[source]

Calculate the Art. 7.p) exempt amount and return a CasillaObservation.

Formula (Ley 35/2006 Art. 7.p) BOE-A-2006-20764):

exempt_amount = min(annual_salary / 365 * qualifying_days, 60_100)

The observation carries legal_refs and source_refs from the registry binding entry so the provenance is traceable from calculation to CLI emit.

Parameters:
  • annual_salary (Decimal) – Gross annual employment salary in EUR (Decimal).

  • qualifying_days (int) – Calendar days of work effectively performed outside Spanish territory within the tax year. Must be in [1, 365].

  • facts (MaritimeWorkerFacts) – Resolved MaritimeWorkerFacts. art_7p_eligible must be True; raises RentaValidationError otherwise.

Return type:

CasillaObservation

Returns:

CasillaObservation for the renta exenta casilla with the exempt amount and full legal provenance.

Raises:

RentaValidationError – When eligibility predicate is not satisfied or when input values are out of range.

calculate_rebeca_exemption(*, gross_navigation_income, facts)[source]

Calculate the REBECA 50% exempt amount and return a typed CasillaObservation.

Formula (Ley 19/1994 Arts. 73-75 BOE-A-1994-15794):

exempt_amount = gross_navigation_income * 0.50

The 50% fraction is statutory and not variable by election.

Parameters:
  • gross_navigation_income (Decimal) – Total gross employment income from navigation in EUR (Decimal). Must be positive.

  • facts (MaritimeWorkerFacts) – Resolved MaritimeWorkerFacts. rebeca_eligible must be True; raises RentaValidationError otherwise.

Return type:

CasillaObservation

Returns:

CasillaObservation for the renta exenta casilla with the exempt amount and full legal provenance.

Raises:

RentaValidationError – When eligibility predicate is not satisfied or when input values are out of range.

guard_da41_inactive(facts)[source]

Raise MaritimeExemptionInactiveError if DA 41 selector resolves True.

This function must be called before any code path that would produce DA 41 exempt-income output. DA 41 requires EU state-aid clearance not granted as of 2024/2025; silently producing an exempt amount would be legally incorrect.

Parameters:

facts (MaritimeWorkerFacts) – Resolved MaritimeWorkerFacts.

Raises:

MaritimeExemptionInactiveError – When da41_eligible returns True.

Return type:

None

check_retmar_mandatory_filing(facts)[source]

Raise ProfileCompletenessError when retmar_registered is True.

Since 2023, all workers registered in the maritime special Social Security regime must file an IRPF declaration regardless of income level (Ley 35/2006 art. 96, BOE-A-2006-20764). This is a completeness gate only — it must not suppress further processing or alter casilla values.

Callers should catch ProfileCompletenessError, surface the message to the operator, and continue processing.

Parameters:

facts (MaritimeWorkerFacts) – Resolved MaritimeWorkerFacts.

Raises:

ProfileCompletenessError – When retmar_registered is True.

Return type:

None