feat: CAT-002 Regla de Oro Rama vs Hoja + validaciones #35

Merged
dmolinari merged 9 commits from feature/CAT-002 into main 2026-04-19 11:56:32 +00:00
4 changed files with 55 additions and 10 deletions
Showing only changes of commit 9e50a929ae - Show all commits

View File

@@ -12,7 +12,8 @@ public static class RubroTreeBuilder
{ {
public static IReadOnlyList<RubroTreeNodeDto> Build( public static IReadOnlyList<RubroTreeNodeDto> Build(
IEnumerable<Rubro> flat, IEnumerable<Rubro> flat,
bool incluirInactivos) bool incluirInactivos,
IReadOnlyDictionary<int, int> avisoCounts)
{ {
var filtered = incluirInactivos var filtered = incluirInactivos
? flat.ToList() ? flat.ToList()
@@ -36,6 +37,7 @@ public static class RubroTreeBuilder
Activo: r.Activo, Activo: r.Activo,
ParentId: r.ParentId, ParentId: r.ParentId,
TarifarioBaseId: r.TarifarioBaseId, TarifarioBaseId: r.TarifarioBaseId,
TieneAvisos: avisoCounts.GetValueOrDefault(r.Id, 0) > 0,
Hijos: children); Hijos: children);
} }

View File

@@ -10,4 +10,5 @@ public sealed record RubroTreeNodeDto(
bool Activo, bool Activo,
int? ParentId, int? ParentId,
int? TarifarioBaseId, int? TarifarioBaseId,
bool TieneAvisos,
IReadOnlyList<RubroTreeNodeDto> Hijos); IReadOnlyList<RubroTreeNodeDto> Hijos);

View File

@@ -17,6 +17,7 @@ public sealed class GetRubroTreeQueryHandler : ICommandHandler<GetRubroTreeQuery
public async Task<IReadOnlyList<RubroTreeNodeDto>> Handle(GetRubroTreeQuery query) public async Task<IReadOnlyList<RubroTreeNodeDto>> Handle(GetRubroTreeQuery query)
{ {
var all = await _repo.GetAllAsync(query.IncluirInactivos); var all = await _repo.GetAllAsync(query.IncluirInactivos);
return RubroTreeBuilder.Build(all, query.IncluirInactivos); // CAT-002: avisoCounts injected via IAvisoQueryRepository (wired in Batch 6)
return RubroTreeBuilder.Build(all, query.IncluirInactivos, new Dictionary<int, int>());
} }
} }

View File

@@ -18,7 +18,7 @@ public class RubroTreeBuilderTests
[Fact] [Fact]
public void Build_empty_returns_empty_list() public void Build_empty_returns_empty_list()
{ {
var result = RubroTreeBuilder.Build([], incluirInactivos: false); var result = RubroTreeBuilder.Build([], incluirInactivos: false, new Dictionary<int, int>());
result.Should().BeEmpty(); result.Should().BeEmpty();
} }
@@ -30,7 +30,7 @@ public class RubroTreeBuilderTests
{ {
var rubros = new[] { MakeRubro(1, null, "Autos", 0) }; var rubros = new[] { MakeRubro(1, null, "Autos", 0) };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
result.Should().HaveCount(1); result.Should().HaveCount(1);
result[0].Id.Should().Be(1); result[0].Id.Should().Be(1);
@@ -50,7 +50,7 @@ public class RubroTreeBuilderTests
MakeRubro(2, null, "Camiones", 1) MakeRubro(2, null, "Camiones", 1)
}; };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
result.Should().HaveCount(3); result.Should().HaveCount(3);
result[0].Id.Should().Be(1); // Orden=0 result[0].Id.Should().Be(1); // Orden=0
@@ -70,7 +70,7 @@ public class RubroTreeBuilderTests
MakeRubro(3, 2, "Compactos", 0), MakeRubro(3, 2, "Compactos", 0),
}; };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
result.Should().HaveCount(1); result.Should().HaveCount(1);
result[0].Id.Should().Be(1); result[0].Id.Should().Be(1);
@@ -91,7 +91,7 @@ public class RubroTreeBuilderTests
MakeRubro(2, null, "Motos", 1, activo: false), MakeRubro(2, null, "Motos", 1, activo: false),
}; };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
result.Should().HaveCount(1); result.Should().HaveCount(1);
result[0].Id.Should().Be(1); result[0].Id.Should().Be(1);
@@ -106,7 +106,7 @@ public class RubroTreeBuilderTests
MakeRubro(2, null, "Motos", 1, activo: false), MakeRubro(2, null, "Motos", 1, activo: false),
}; };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: true); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: true, new Dictionary<int, int>());
result.Should().HaveCount(2); result.Should().HaveCount(2);
} }
@@ -125,7 +125,7 @@ public class RubroTreeBuilderTests
MakeRubro(5, 1, "A", 0), MakeRubro(5, 1, "A", 0),
}; };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
var hijos = result[0].Hijos; var hijos = result[0].Hijos;
hijos.Should().HaveCount(4); hijos.Should().HaveCount(4);
@@ -135,6 +135,47 @@ public class RubroTreeBuilderTests
hijos[3].Nombre.Should().Be("D"); // Orden=3 hijos[3].Nombre.Should().Be("D"); // Orden=3
} }
// ── TieneAvisos from avisoCounts dict ────────────────────────────────────
[Fact]
public void Build_SetsTieneAvisos_True_WhenCountGreaterThanZero()
{
var rubros = new[]
{
MakeRubro(1, null, "Autos", 0),
MakeRubro(2, null, "Motos", 1),
};
var avisoCounts = new Dictionary<int, int> { { 1, 2 }, { 2, 0 } };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, avisoCounts);
result.Single(n => n.Id == 1).TieneAvisos.Should().BeTrue();
result.Single(n => n.Id == 2).TieneAvisos.Should().BeFalse();
}
[Fact]
public void Build_SetsTieneAvisos_False_WhenCountIsZero()
{
var rubros = new[] { MakeRubro(1, null, "Autos", 0) };
var avisoCounts = new Dictionary<int, int> { { 1, 0 } };
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, avisoCounts);
result[0].TieneAvisos.Should().BeFalse();
}
[Fact]
public void Build_SetsTieneAvisos_False_WhenIdMissingFromDict()
{
// Stub semantics: missing key = 0 = false
var rubros = new[] { MakeRubro(1, null, "Autos", 0) };
var avisoCounts = new Dictionary<int, int>(); // empty dict
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, avisoCounts);
result[0].TieneAvisos.Should().BeFalse();
}
// ── O(n) perf smoke test ────────────────────────────────────────────────── // ── O(n) perf smoke test ──────────────────────────────────────────────────
[Fact] [Fact]
@@ -148,7 +189,7 @@ public class RubroTreeBuilderTests
rubros.Add(MakeRubro(i, 1, $"Child{i}", i - 2)); rubros.Add(MakeRubro(i, 1, $"Child{i}", i - 2));
var sw = System.Diagnostics.Stopwatch.StartNew(); var sw = System.Diagnostics.Stopwatch.StartNew();
var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false); var result = RubroTreeBuilder.Build(rubros, incluirInactivos: false, new Dictionary<int, int>());
sw.Stop(); sw.Stop();
result.Should().HaveCount(1); result.Should().HaveCount(1);