You can add an extra symbol layer of Geometry Generator type

Expression
case
when
$id = array_max (
array_agg(
$id,
group_by:="batalhao"
)
)
then
buffer(
collect(
$geometry,
group_by:="batalhao"
),
0 -- the 0 distance buffer is to "dissolve" the collected geometries
)
end
Thanks to the very helpful input from @JGH, the expression could be optimised (using this solution). The case statement ensures that the collection and buffering only occurs once per macroregion, rather than once for every microregion.
Result

Notes
The symbol layer disappears when the canvas is panned or zoomed so that the microregion with the maximum id (per macroregion) is completely off the screen.

Whereas array_max places the macroregion boundary on top of the microregions, using array_min in the when clause of the case statement places the macroregion boundary beneath the microregions. Except for the boundary of the microregion with the minimum id. This is visible when using a light colour for the Geometry Generator stroke.
