using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM2.Api.Authorization;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Rubros.Create;
using SIGCM2.Application.Rubros.Deactivate;
using SIGCM2.Application.Rubros.Dtos;
using SIGCM2.Application.Rubros.GetById;
using SIGCM2.Application.Rubros.GetTree;
using SIGCM2.Application.Rubros.Move;
using SIGCM2.Application.Rubros.Update;
namespace SIGCM2.Api.Controllers;
///
/// CAT-001: Rubro N-ary tree management.
/// Read endpoints at /api/v1/rubros — require authentication (any role).
/// Write endpoints at /api/v1/admin/rubros — require 'catalogo:rubros:gestionar'.
///
[ApiController]
public sealed class RubrosController : ControllerBase
{
private readonly IDispatcher _dispatcher;
public RubrosController(IDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
// ── READ endpoints ─────────────────────────────────────────────────────────
/// Returns the full Rubro tree. Requires authentication.
[HttpGet("api/v1/rubros/tree")]
[Authorize]
[ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task GetRubroTree([FromQuery] bool incluirInactivos = false)
{
var query = new GetRubroTreeQuery(incluirInactivos);
var result = await _dispatcher.Send>(query);
return Ok(result);
}
/// Returns a single Rubro by id. Requires authentication.
[HttpGet("api/v1/rubros/{id:int}")]
[Authorize]
[ProducesResponseType(typeof(RubroDetailDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task GetRubroById([FromRoute] int id)
{
var query = new GetRubroByIdQuery(id);
var result = await _dispatcher.Send(query);
return Ok(result);
}
// ── WRITE endpoints ────────────────────────────────────────────────────────
/// Creates a new Rubro. Requires catalogo:rubros:gestionar.
[HttpPost("api/v1/admin/rubros")]
[RequirePermission("catalogo:rubros:gestionar")]
[ProducesResponseType(typeof(RubroCreatedDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
public async Task CreateRubro([FromBody] CreateRubroRequest request)
{
var command = new CreateRubroCommand(
Nombre: request.Nombre ?? string.Empty,
ParentId: request.ParentId,
TarifarioBaseId: request.TarifarioBaseId);
var result = await _dispatcher.Send(command);
return CreatedAtAction(nameof(GetRubroById), new { id = result.Id }, result);
}
/// Updates a Rubro's nombre. Requires catalogo:rubros:gestionar.
[HttpPut("api/v1/admin/rubros/{id:int}")]
[RequirePermission("catalogo:rubros:gestionar")]
[ProducesResponseType(typeof(RubroUpdatedDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task UpdateRubro([FromRoute] int id, [FromBody] UpdateRubroRequest request)
{
var command = new UpdateRubroCommand(
Id: id,
Nombre: request.Nombre ?? string.Empty);
var result = await _dispatcher.Send(command);
return Ok(result);
}
/// Soft-deletes (deactivates) a Rubro. Requires catalogo:rubros:gestionar.
[HttpDelete("api/v1/admin/rubros/{id:int}")]
[RequirePermission("catalogo:rubros:gestionar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task DeactivateRubro([FromRoute] int id)
{
var command = new DeactivateRubroCommand(id);
await _dispatcher.Send(command);
return NoContent();
}
/// Moves a Rubro to a new parent. Requires catalogo:rubros:gestionar.
[HttpPatch("api/v1/admin/rubros/{id:int}/mover")]
[RequirePermission("catalogo:rubros:gestionar")]
[ProducesResponseType(typeof(RubroMovedDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
public async Task MoveRubro([FromRoute] int id, [FromBody] MoveRubroRequest request)
{
var command = new MoveRubroCommand(
Id: id,
NuevoParentId: request.NuevoParentId,
NuevoOrden: request.NuevoOrden);
var result = await _dispatcher.Send(command);
return Ok(result);
}
}
// ── Request body records ──────────────────────────────────────────────────────
/// CAT-001: Create rubro request body.
public sealed record CreateRubroRequest(
string? Nombre,
int? ParentId,
int? TarifarioBaseId);
/// CAT-001: Update rubro request body.
public sealed record UpdateRubroRequest(
string? Nombre);
/// CAT-001: Move rubro request body.
public sealed record MoveRubroRequest(
int? NuevoParentId,
int NuevoOrden);