code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Helpers\Env;
use App\Models\GroupModel;
use App\Models\PlayerModel;
use App\Models\RegistrationCategoryModel;
use App\Validations\GroupValidation;
use App\Validations\PlayerValidation;
use App\Validations\RegistrationCategoryValidation;
use Httpful\Exception\ConnectionErrorException;
use Httpful\Request;

class AnalysisController extends BaseController
{
    private const ENV_VARNAME = 'APP_DATA_MODEL_URL';

    /**
     * Obtiene la url del modelo de datos.
     */
    private function getUrl(): string
    {
        return rtrim(Env::get(self::ENV_VARNAME), '/');
    }

    /**
     * Obtiene el "perfil" del "jugador".
     */
    public function profile(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = PlayerValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The player identifier is incorrect');
        }

        // Consulta la información del "jugador".
        $player = new PlayerModel;
        $player->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "jugador".
        if (! $player->isHydrated()) {
            $this->respondNotFound('The player information was not found');
        }

        // Construye la url de la petición.
        $url = sprintf('%s/profile/%s', $this->getUrl(), $id);

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the player profile');
    }

    /**
     * Obtiene el "mapa de calor" de los "jugadores".
     */
    public function heatmap(): void
    {
        // Construye la url de la petición.
        $url = sprintf('%s/map', $this->getUrl());

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the heatmap players');
    }

    /**
     * Obtiene la gráfica de "paridad" de una "categoría de inscripción".
     */
    public function parity(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = RegistrationCategoryValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The registration category identifier is incorrect');
        }

        // Consulta la información de la "categoría de inscripción".
        $registrationCategory = new RegistrationCategoryModel;
        $registrationCategory->select('id')->eq('id', $id)->find();

        // Construye la url de la petición.
        $url = sprintf('%s/parity/%s', $this->getUrl(), $registrationCategory->id);

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the registration category parity');
    }

    /**
     * Obtiene la gráfica de "sinergia" de una "categoría de inscripción".
     */
    public function synergy(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = RegistrationCategoryValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The registration category identifier is incorrect');
        }

        // Consulta la información de la "categoría de inscripción".
        $registrationCategory = new RegistrationCategoryModel;
        $registrationCategory->select('id')->eq('id', $id)->find();

        // Construye la url de la petición.
        $url = sprintf('%s/sinergy/%s', $this->getUrl(), $registrationCategory->id);

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the registration category synergy');
    }

    /**
     * Obtiene la gráfica de comparación de las "sinergias"
     * de las "categorías de inscripción".
     */
    public function compareSynergies(): void
    {
        // Construye la url de la petición.
        $url = sprintf('%s/sinergy_compare', $this->getUrl());

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the synergies comparison');
    }

    /**
     * Obtiene la gráfica de comparación de los puntos realizados
     * en las "categorías de inscripción".
     */
    public function comparePoints(): void
    {
        // Construye la url de la petición.
        $url = sprintf('%s/points_compare', $this->getUrl());

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the points comparison');
    }

    /**
     * Obtiene la gráfica de comparación de los puntos realizados por "grupo".
     */
    public function groupPoints(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id', 'registration_category_id', 'description')
            ->eq('id', $id)
            ->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Construye la url de la petición.
        $url = sprintf('%s/points/%s/%s', $this->getUrl(), $group->registration_category_id, $group->description);

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the group points');
    }

    /**
     * Obtiene el análisis del partido final del torneo.
     */
    public function feelings(): void
    {
        // Construye la url de la petición.
        $url = sprintf('%s/feelings', $this->getUrl());

        try {
            // Realiza la petición.
            $response = Request::get($url)->expectsJson()->send();
        } catch (ConnectionErrorException $e) {
            $this->respondServiceUnavailable($e->getMessage());
        }

        $this->respond($response->body ?? null, 'Information about the feelings of the final');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use Throwable;

class ApiController extends BaseController
{
    public function welcome(): void
    {
        $this->respond([
            'name' => 'API RESTful del evento de frontenis "Torneo de las Fresas Irapuato 2025"',
            'documentation' => 'https://astra-exe.github.io/TDLF2025/api/',
            'repository' => 'https://github.com/Astra-exe/TDLF2025',
            'license' => 'AGPL-3.0-or-later',
            'authors' => [
                [
                    'fullname' => 'Ricardo García Jiménez',
                    'email' => 'ricardogj08@riseup.net',
                    'linkedin' => 'https://www.linkedin.com/in/ricardogj08/',
                    'repository' => 'https://notabug.org/ricardogj08/',
                    'role' => 'Backend developer',
                ],
                [
                    'fullname' => 'Francisco Javier Solís Martínez',
                    'email' => 'franciscosolism08@gmail.com',
                    'linkedin' => 'https://www.linkedin.com/in/francisco-js/',
                    'repository' => 'https://github.com/francisco-solis99',
                    'role' => 'Frontend developer',
                ],
                [
                    'fullname' => 'Juan José Ramírez López',
                    'email' => 'juan.ramirez.j99@gmail.com',
                    'linkedin' => 'https://www.linkedin.com/in/juan-rl/',
                    'repository' => 'https://github.com/Astra-exe/',
                    'role' => 'Data scientist',
                ],
            ],
        ], 'Information about the API RESTful');
    }

    /**
     * Respuestas para endpoint que no existen.
     */
    public function notFound(): void
    {
        $this->respondNotFound('The endpoint was not found');
    }

    /**
     * Respuestas de errores.
     */
    public function errors(Throwable $error): void
    {
        $this->respondServerError($error->getTrace(), $error->getMessage());
    }
}

license: mit


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Helpers\Auth;
use App\Helpers\Password;
use App\Models\ApiKeyModel;
use App\Models\UserModel;
use App\Validations\UserValidation;

class AuthController extends BaseController
{
    /**
     * Genera la "API key" de inicio de sesión.
     */
    public function login(): void
    {
        // Define los campos necesarios de la petición.
        $requestFields = ['nickname', 'password'];

        $data = [];

        // Obtiene solo los campos necesarios.
        foreach ($requestFields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;
        }

        // Define los campos necesarios del "usuario de acceso".
        $userFields = ['password'];

        // Modo de autenticación por defecto.
        $identifyBy = 'username';

        // Comprueba el modo de autenticación.
        if ($this->gump()->is_valid($data, ['nickname' => 'required|valid_email']) === true) {
            $identifyBy = 'email';
        }

        $userFields[] = $identifyBy;

        // Obtiene las reglas de validación.
        $rules = UserValidation::getRules($userFields);

        $rules['nickname'] = $rules[$identifyBy];
        unset($rules[$identifyBy]);

        // Define todas las reglas de validación como obligatorias.
        foreach (array_keys($rules) as $rule) {
            array_unshift($rules[$rule], 'required');
        }

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Establece los filtros de validación.
        $this->gump()->filter_rules(UserValidation::getFilters($userFields));

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The access credentials information is incorrect');
        }

        // Consulta la información del "usuario de acceso" que intenta autenticarse.
        $user = new UserModel;
        $user->select('id', 'password')
            ->eq($identifyBy, $data['nickname'])
            ->eq('is_active', 1)
            ->find();

        // Comprueba si el "usuario de acceso" que intenta autenticarse existe.
        if (! $user->isHydrated() || ! Password::verify($data['password'], $user->password)) {
            $this->respondUnauthorized('The access credentials are invalid');
        }

        // Elimina la antigua "API key" del "usuario de acceso" si existe.
        $user->apiKey->delete();

        // Genera la key de autenticación.
        $key = Auth::generateKey();

        // Registra la "API key" del "usuario de acceso".
        $apiKey = new ApiKeyModel;
        $apiKey->copyFrom(['user_id' => $user->id, 'hash' => Auth::generateHash($key)]);
        $apiKey->insert();

        $this->respondCreated(['api_key' => $key], 'The API key was created successfully');
    }

    /**
     * Muestra la información del "usuario de acceso" autenticado.
     */
    public function me(): void
    {
        $this->respond($this->userAuth(), 'Information about the authenticated user');
    }

    /**
     * Muestra la información de la "API key" del "usuario de acceso" autenticado.
     */
    public function check(): void
    {
        $this->respond($this->userAuth()->apiKey, 'Information about the API key');
    }

    /**
     * Regenera la "API key" del "usuario de acceso" autenticado.
     */
    public function refresh(): void
    {
        // Elimina la antigua API key del "usuario de acceso".
        $this->userAuth()->apiKey->delete();

        // Genera la nueva key de autenticación.
        $key = Auth::generateKey();

        // Regenera la API key del "usuario de acceso".
        $apiKey = new ApiKeyModel;
        $apiKey->copyFrom(['user_id' => $this->userAuth()->id, 'hash' => Auth::generateHash($key)]);
        $apiKey->insert();

        $this->respondCreated(['api_key' => $key], 'The API key was renewed successfully');
    }

    /**
     * Elimina la "API key" del "usuario de acceso" autenticado.
     */
    public function logout(): void
    {
        $this->userAuth()->apiKey->delete();
        $this->respondNoContent('The API key was finalized successfully');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Traits\HelperTrait;
use App\Traits\ResponseTrait;
use flight\Engine;

/**
 * Clase base para todos los Controladores.
 */
abstract class BaseController
{
    use HelperTrait;
    use ResponseTrait;

    private Engine $app;

    /**
     * Constructor de la clase.
     */
    public function __construct(Engine $app)
    {
        $this->app = $app;
    }

    /**
     * Obtiene la instancia de la aplicación.
     */
    protected function app(): Engine
    {
        return $this->app;
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Helpers\Database;
use App\Models\GroupModel;
use App\Models\PairModel;
use App\Models\PairPlayerPivotModel;
use App\Validations\GroupValidation;

class GroupController extends BaseController
{
    /**
     * Muestra la información de todos los "grupos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'search' => '',
            'filterBy' => 'name',
            'orderBy' => 'name',
            'sortBy' => 'asc',
            'registration_category_id' => null,
            'is_eliminated' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(GroupValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(GroupValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The groups search information is incorrect');
        }

        // Consulta la información de todos los "grupos" con paginación.
        $groupModel = new GroupModel;
        $groupModel->like($queryParams['filterBy'], sprintf('%%%s%%', $queryParams['search']));

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'is_eliminated', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $groupModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $groupModel->paginate($queryParams['page']);
        $pagination = $groupModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $groupModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta la "categoría de inscripción" de cada "grupo".
        $groups = array_map(static function (GroupModel $group): GroupModel {
            $group->setCustomData('registration_category', $group->registrationCategory);
            unset($group->registration_category_id);

            return $group;
        }, $groupModel->findAll());

        $this->respondPagination($groups, $pagination, 'Information about all the groups with pagination');
    }

    /**
     * Muestra la información de un "grupo".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->find($id);

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta la "categoría de inscripción" del "grupo".
        $group->setCustomData('registration_category', $group->registrationCategory);
        unset($group->registration_category_id);

        $this->respond($group, 'Information about the group');
    }

    /**
     * Modifica la información de un "grupo".
     */
    public function update(string $id): void
    {
        // Define los campos que se pueden modificar.
        $fields = ['is_eliminated', 'is_active'];

        $data = ['id' => $id];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;

            if (is_null($data[$field])) {
                unset($data[$field]);
            }
        }

        $fieldNames = array_keys($data);

        // Obtiene las reglas de validación
        // y establece el identificador como obligatorio.
        $rules = GroupValidation::getRules(['id', ...$fieldNames]);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Establece los filtros de validación.
        $this->gump()->filter_rules(GroupValidation::getFilters($fieldNames));

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        unset($data['id']);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group information is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Modifica la información del "grupo".
        if (! empty($data)) {
            $group->copyFrom($data);
            $group->update();
            $group->reset();
        }

        // Consulta la información actualizada del "grupo".
        $group->find($id);

        // Consulta la "categoría de inscripción" del "grupo".
        $group->setCustomData('registration_category', $group->registrationCategory);
        unset($group->registration_category_id);

        $this->respond($group, 'The group was updated successfully');
    }

    /**
     * Ranking de las "parejas" y "jugadores" del "grupo".
     */
    public function ranking(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta el ranking de las "parejas" del "grupo".
        $ranking = (new PairModel)->select('pairs.*', 'SUM(CASE WHEN matches_pairs.is_winner = 1 THEN 1 ELSE 0 END) AS total_winners', 'SUM(matches_pairs.score) AS total_score')
            ->from('groups')
            ->join('groups_matches', 'groups.id = groups_matches.group_id', 'INNER') // Partidos del grupo.
            ->join('matches_pairs', 'groups_matches.match_id = matches_pairs.match_id', 'INNER') // Parejas contrincantes del partido.
            ->join('pairs', 'matches_pairs.pair_id = pairs.id', 'INNER') // Parejas del partido.
            ->where(sprintf('groups.id = %s', Database::getConnection()->quote($id)))
            ->groupBy('pairs.id')
            ->orderBy('total_winners DESC', 'total_score DESC')
            ->findAll();

        // Obtiene la información de las "parejas".
        $pairs = array_map(static function (PairModel $pair): array {
            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);

            // Consulta los "jugadores" de la "pareja".
            $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                'player' => $pairPlayerRel->player,
                'relationship' => $pairPlayerRel,
            ], $pair->pairPlayerPivot));

            // Obtiene los datos de "clasificación" de la "pareja".
            $rating = ['total_winners' => $pair->total_winners, 'total_score' => $pair->total_score];

            unset($pair->registration_category_id, $pair->total_winners, $pair->total_score);

            return ['pair' => $pair, 'rating' => $rating];
        }, $ranking);

        $this->respond($pairs, 'Information about the group ranking');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\GroupMatchPivotModel;
use App\Models\GroupModel;
use App\Validations\GroupValidation;

class GroupMatchController extends BaseController
{
    /**
     * Muestra la información de todos los "partidos" de los "grupos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'search' => '',
            'filterBy' => 'name',
            'orderBy' => 'name',
            'sortBy' => 'asc',
            'registration_category_id' => null,
            'is_eliminated' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(GroupValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(GroupValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The groups matches search information is incorrect');
        }

        // Consulta la información de todos los "grupos" con paginación.
        $groupModel = new GroupModel;
        $groupModel->like($queryParams['filterBy'], sprintf('%%%s%%', $queryParams['search']));

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'is_eliminated', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $groupModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $groupModel->paginate($queryParams['page']);
        $pagination = $groupModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $groupModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta los "grupos" de la búsqueda.
        $groups = array_map(static function (GroupModel $group): array {
            // Consulta la "categoría de inscripción" de cada "grupo".
            $group->setCustomData('registration_category', $group->registrationCategory);
            unset($group->registration_category_id);

            // Consulta la información de los "partidos" del "grupo".
            $matches = array_map(static function (GroupMatchPivotModel $groupMatchRel): array {
                $match = $groupMatchRel->match;
                $match->setCustomData('registration_category', $match->registrationCategory);
                $match->setCustomData('match_category', $match->matchCategory);
                $match->setCustomData('match_status', $match->matchStatus);

                unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

                return ['match' => $match, 'relationship' => $groupMatchRel];
            }, $group->groupMatchPivot);

            return ['group' => $group, 'matches' => $matches];
        }, $groupModel->findAll());

        $this->respondPagination($groups, $pagination, 'Information about all the groups matches with pagination');
    }

    /**
     * Muestra la información de los "partidos" de un "grupo".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta la información de los "partidos" del "grupo".
        $matches = array_map(static function (GroupMatchPivotModel $groupMatchRel): array {
            $match = $groupMatchRel->match;
            $match->setCustomData('registration_category', $match->registrationCategory);
            $match->setCustomData('match_category', $match->matchCategory);
            $match->setCustomData('match_status', $match->matchStatus);

            unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

            return ['match' => $match, 'relationship' => $groupMatchRel];
        }, $group->groupMatchPivot);

        $this->respond($matches, 'Information about the group matches');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\GroupMatchPivotModel;
use App\Models\GroupModel;
use App\Models\MatchPairPivotModel;
use App\Models\PairPlayerPivotModel;
use App\Validations\GroupValidation;

class GroupMatchPairPlayerController extends BaseController
{
    private const LIMIT = 4;

    /**
     * Muestra la información de todos los "partidos",
     * "parejas" y "jugadores" de los "grupos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'search' => '',
            'filterBy' => 'name',
            'orderBy' => 'name',
            'sortBy' => 'asc',
            'registration_category_id' => null,
            'is_eliminated' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(GroupValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(GroupValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The groups matches search information is incorrect');
        }

        // Consulta la información de todos los "grupos" con paginación.
        $groupModel = new GroupModel;
        $groupModel->like($queryParams['filterBy'], sprintf('%%%s%%', $queryParams['search']));

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'is_eliminated', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $groupModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $groupModel->paginate($queryParams['page'], self::LIMIT);
        $pagination = $groupModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $groupModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta los "grupos" de la búsqueda.
        $groups = array_map(static function (GroupModel $group): array {
            // Consulta la "categoría de inscripción" de cada "grupo".
            $group->setCustomData('registration_category', $group->registrationCategory);
            unset($group->registration_category_id);

            // Consulta la información de los "partidos" del "grupo".
            $matches = array_map(static function (GroupMatchPivotModel $groupMatchRel): array {
                $match = $groupMatchRel->match;
                $match->setCustomData('registration_category', $match->registrationCategory);
                $match->setCustomData('match_category', $match->matchCategory);
                $match->setCustomData('match_status', $match->matchStatus);

                unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

                // Consulta la información de las "parejas" del "partido".
                $match->setCustomData('pairs', array_map(static function (MatchPairPivotModel $matchPairRel): array {
                    $pair = $matchPairRel->pair;

                    // Consulta la "categoría de inscripción" de la "pareja".
                    $pair->setCustomData('registration_category', $pair->registrationCategory);
                    unset($pair->registration_category_id);

                    // Consulta la información de los "jugadores" de la "pareja".
                    $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                        'player' => $pairPlayerRel->player,
                        'relationship' => $pairPlayerRel,
                    ], $pair->pairPlayerPivot));

                    return ['pair' => $pair, 'relationship' => $matchPairRel];
                }, $match->matchPairPivot));

                return ['match' => $match, 'relationship' => $groupMatchRel];
            }, $group->groupMatchPivot);

            return ['group' => $group, 'matches' => $matches];
        }, $groupModel->findAll());

        $this->respondPagination($groups, $pagination, 'Information about all the groups matches and pairs players with pagination');
    }

    /**
     * Muestra la información de los "partidos"
     * "parejas" y "jugadores" del "grupo".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta la información de los "partidos" del "grupo".
        $matches = array_map(static function (GroupMatchPivotModel $groupMatchRel): array {
            $match = $groupMatchRel->match;
            $match->setCustomData('registration_category', $match->registrationCategory);
            $match->setCustomData('match_category', $match->matchCategory);
            $match->setCustomData('match_status', $match->matchStatus);

            unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

            // Consulta la información de las "parejas" del "partido".
            $match->setCustomData('pairs', array_map(static function (MatchPairPivotModel $matchPairRel): array {
                $pair = $matchPairRel->pair;

                // Consulta la "categoría de inscripción" de la "pareja".
                $pair->setCustomData('registration_category', $pair->registrationCategory);
                unset($pair->registration_category_id);

                // Consulta la información de los "jugadores" de la "pareja".
                $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                    'player' => $pairPlayerRel->player,
                    'relationship' => $pairPlayerRel,
                ], $pair->pairPlayerPivot));

                return ['pair' => $pair, 'relationship' => $matchPairRel];
            }, $match->matchPairPivot));

            return ['match' => $match, 'relationship' => $groupMatchRel];
        }, $group->groupMatchPivot);

        $this->respond($matches, 'Information about the group matches with pairs players');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\GroupModel;
use App\Models\GroupPairPivotModel;
use App\Models\PairPlayerPivotModel;
use App\Validations\GroupValidation;

class GroupPairController extends BaseController
{
    /**
     * Muestra la información de todas las "parejas de los grupos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'search' => '',
            'filterBy' => 'name',
            'orderBy' => 'name',
            'sortBy' => 'asc',
            'registration_category_id' => null,
            'is_eliminated' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(GroupValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(GroupValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The groups pairs search information is incorrect');
        }

        // Consulta la información de todos los "grupos" con paginación.
        $groupModel = new GroupModel;
        $groupModel->like($queryParams['filterBy'], sprintf('%%%s%%', $queryParams['search']));

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'is_eliminated', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $groupModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $groupModel->paginate($queryParams['page']);
        $pagination = $groupModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $groupModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta los "grupos" de la búsqueda.
        $groups = array_map(static function (GroupModel $group): array {
            // Consulta la "categoría de inscripción" del "grupo".
            $group->setCustomData('registration_category', $group->registrationCategory);
            unset($group->registration_category_id);

            // Consulta la información de las "parejas" del "grupo".
            $pairs = array_map(static function (GroupPairPivotModel $groupPairRel): array {
                $pair = $groupPairRel->pair;

                // Consulta la "categoría de inscripción" de la "pareja".
                $pair->setCustomData('registration_category', $pair->registrationCategory);
                unset($pair->registration_category_id);

                return ['pair' => $pair, 'relationship' => $groupPairRel];
            }, $group->groupPairPivot);

            return ['group' => $group, 'pairs' => $pairs];
        }, $groupModel->findAll());

        $this->respondPagination($groups, $pagination, 'Information about all the groups pairs with pagination');
    }

    /**
     * Muestra la información de las "parejas de un grupo".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta la información de las "parejas" del "grupo".
        $pairs = array_map(static function (GroupPairPivotModel $groupPairRel): array {
            $pair = $groupPairRel->pair;

            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            return ['pair' => $pair, 'relationship' => $groupPairRel];
        }, $group->groupPairPivot);

        $this->respond($pairs, 'Information about the group pairs');
    }

    /**
     * Muestra la información de todas las "parejas" y "jugadores" del "grupo".
     */
    public function players(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = GroupValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The group identifier is incorrect');
        }

        // Consulta la información del "grupo".
        $group = new GroupModel;
        $group->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "grupo".
        if (! $group->isHydrated()) {
            $this->respondNotFound('The group information was not found');
        }

        // Consulta la información de las "parejas" del "grupo".
        $pairs = array_map(static function (GroupPairPivotModel $groupPairRel): array {
            $pair = $groupPairRel->pair;

            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            // Consulta la información de los "jugadores" de la "pareja".
            $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                'player' => $pairPlayerRel->player,
                'relationship' => $pairPlayerRel,
            ], $pair->pairPlayerPivot));

            return ['pair' => $pair, 'relationship' => $groupPairRel];
        }, $group->groupPairPivot);

        $this->respond($pairs, 'Information about the group pairs with players');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\MatchCategoryModel;
use App\Validations\MatchCategoryValidation;

class MatchCategoryController extends BaseController
{
    /**
     * Muestra la información de todas las
     * "categorías de las rondas de los partidos".
     */
    public function index(): void
    {
        $categories = (new MatchCategoryModel)->orderBy('description ASC')->findAll();
        $this->respond($categories, 'Information about all the matches categories');
    }

    /**
     * Muestra la información de una
     * "categoría de las rondas de los partidos".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchCategoryValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match category identifier is incorrect');
        }

        // Consulta la información de la "categoría del partido".
        $matchCategory = new MatchCategoryModel;
        $matchCategory->find($id);

        // Comprueba si existe la "categoría del partido".
        if (! $matchCategory->isHydrated()) {
            $this->respondNotFound('The match category information was not found');
        }

        $this->respond($matchCategory, 'Information about the match category');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\MatchModel;
use App\Models\MatchPairPivotModel;
use App\Models\PairModel;
use App\Validations\MatchValidation;

class MatchController extends BaseController
{
    /**
     * Muestra la información de todos los "partidos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'orderBy' => 'created_at',
            'sortBy' => 'desc',
            'registration_category_id' => null,
            'match_category_id' => null,
            'match_status_id' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(MatchValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(MatchValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The matches search information is incorrect');
        }

        // Consulta la información de todos los "partidos" con paginación.
        $matchModel = new MatchModel;

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'match_category_id', 'match_status_id', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $matchModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $matchModel->paginate($queryParams['page']);
        $pagination = $matchModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $matchModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta la información de la categoría de inscripción,
        // categoría del partido y estatus del partido.
        $matches = array_map(static function (MatchModel $match): MatchModel {
            $match->setCustomData('registration_category', $match->registrationCategory);
            $match->setCustomData('match_category', $match->matchCategory);
            $match->setCustomData('match_status', $match->matchStatus);

            unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

            return $match;
        }, $matchModel->findAll());

        $this->respondPagination($matches, $pagination, 'Information about all the matches with pagination');
    }

    /**
     * Registra la información de un "partido".
     */
    public function create(): void
    {
        // Define los campos necesarios de la petición.
        $fields = ['registration_category_id', 'match_category_id', 'match_status_id', 'pairs'];

        $data = [];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;
        }

        // Obtiene las reglas de validación del "partido".
        $rules = MatchValidation::getRules($fields);

        // Define todas las reglas de validación como obligatorias.
        foreach (array_keys($rules) as $rule) {
            array_unshift($rules[$rule], 'required');
        }

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match information is incorrect');
        }

        // Comprueba la información de las "parejas".
        $dataPairs = (new PairModel)->select('id')
            ->eq('registration_category_id', $data['registration_category_id'])
            ->in('id', $data['pairs'])
            ->findAll();

        // Comprueba que las "parejas" existan.
        if (array_diff($data['pairs'], array_column($dataPairs, 'id'))) {
            $this->respondValidationErrors(
                ['pairs' => 'The pairs were not found'],
                'The pairs information is incorrect');
        }

        // Comprueba que las "parejas" no se repitan.
        if (count($data['pairs']) !== count($dataPairs)) {
            $this->respondValidationErrors(
                ['pairs' => 'The pairs were repeated'],
                'The pairs information is incorrect');
        }

        unset($data['pairs']);

        // Registra la información del "partido".
        $match = new MatchModel;
        $match->copyFrom($data);
        $match->insert();

        $matchPairPivot = new MatchPairPivotModel;

        // Registra la relación de las "parejas" con el "partido".
        foreach ($dataPairs as $dataPair) {
            $matchPairPivot->copyFrom(['pair_id' => $dataPair->id, 'match_id' => $match->id]);
            $matchPairPivot->insert();
            $matchPairPivot->reset();
        }

        $id = $match->id;

        // Consulta la información del "partido" registrado.
        $match->reset();
        $match->find($id);

        $match->setCustomData('registration_category', $match->registrationCategory);
        $match->setCustomData('match_category', $match->matchCategory);
        $match->setCustomData('match_status', $match->matchStatus);

        unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

        // Consulta la información de las "parejas" del "partido".
        $pairs = array_map(static function (MatchPairPivotModel $matchPairRel): array {
            $pair = $matchPairRel->pair;

            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            return ['pair' => $pair, 'relationship' => $matchPairRel];
        }, $match->matchPairPivot);

        $this->respondCreated(['match' => $match, 'pairs' => $pairs], 'The match was created successfully');
    }

    /**
     * Muestra la información de un "partido".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match identifier is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->find($id);

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        $match->setCustomData('registration_category', $match->registrationCategory);
        $match->setCustomData('match_category', $match->matchCategory);
        $match->setCustomData('match_status', $match->matchStatus);

        unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

        $this->respond($match, 'Information about the match');
    }

    /**
     * Muestra la información del "grupo" de un "partido".
     */
    public function group(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match identifier is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        // Obtiene la relación del "grupo" con el "partido".
        $groupMatchRel = $match->groupMatchPivot;

        // Obtiene el "grupo" del "partido".
        $group = $groupMatchRel->_group;

        // Consulta la "categoría de inscripción" del "grupo".
        $group->setCustomData('registration_category', $group->registrationCategory);
        unset($group->registration_category_id);

        $this->respond(['group' => $group, 'relationship' => $groupMatchRel], 'Information about the match group');
    }

    /**
     * Modifica la información de un "partido".
     */
    public function update(string $id): void
    {
        // Define los campos que se pueden modificar.
        $fields = ['match_category_id', 'match_status_id', 'is_active'];

        $data = ['id' => $id];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;

            if (is_null($data[$field])) {
                unset($data[$field]);
            }
        }

        $fieldNames = array_keys($data);

        // Obtiene las reglas de validación
        // y establece el identificador como obligatorio.
        $rules = MatchValidation::getRules(['id', ...$fieldNames]);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Establece los filtros de validación.
        $this->gump()->filter_rules(MatchValidation::getFilters($fieldNames));

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        unset($data['id']);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match information is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        // Modifica la información del "partido".
        if (! empty($data)) {
            $match->copyFrom($data);
            $match->update();
            $match->reset();
        }

        // Consulta la información actualizada del "partido".
        $match->find($id);

        $match->setCustomData('registration_category', $match->registrationCategory);
        $match->setCustomData('match_category', $match->matchCategory);
        $match->setCustomData('match_status', $match->matchStatus);

        unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

        $this->respondUpdated($match, 'The match was updated successfully');
    }

    /**
     * Elimina la información de un "partido".
     */
    public function delete(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match identifier is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->find($id);

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        $match->setCustomData('registration_category', $match->registrationCategory);
        $match->setCustomData('match_category', $match->matchCategory);
        $match->setCustomData('match_status', $match->matchStatus);

        unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

        // Elimina la información del "partido".
        $match->delete();

        $this->respondDeleted($match, 'The match was deleted successfully');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\MatchModel;
use App\Models\MatchPairPivotModel;
use App\Validations\MatchPairValidation;
use App\Validations\MatchValidation;

class MatchPairController extends BaseController
{
    /**
     * Muestra la información de todas las "parejas" de los "partidos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'orderBy' => 'created_at',
            'sortBy' => 'desc',
            'registration_category_id' => null,
            'match_category_id' => null,
            'match_status_id' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(MatchValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(MatchValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The matches pairs search information is incorrect');
        }

        // Consulta la información de todos los "partidos" con paginación.
        $matchModel = new MatchModel;

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'match_category_id', 'match_status_id', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $matchModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $matchModel->paginate($queryParams['page']);
        $pagination = $matchModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $matchModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta la información de las "parejas" de los "partido".
        $matches = array_map(static function (MatchModel $match): array {
            $match->setCustomData('registration_category', $match->registrationCategory);
            $match->setCustomData('match_category', $match->matchCategory);
            $match->setCustomData('match_status', $match->matchStatus);

            unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

            // Consulta la información de las "parejas".
            $pairs = array_map(static function (MatchPairPivotModel $matchPairRel): array {
                $pair = $matchPairRel->pair;

                // Consulta la "categoría de inscripción" de la "pareja".
                $pair->setCustomData('registration_category', $pair->registrationCategory);
                unset($pair->registration_category_id);

                return ['pair' => $pair, 'relationship' => $matchPairRel];
            }, $match->matchPairPivot);

            return ['match' => $match, 'pairs' => $pairs];
        }, $matchModel->findAll());

        $this->respondPagination($matches, $pagination, 'Information about the matches pairs with pagination');
    }

    /**
     * Muestra la información de las "parejas" del "partido".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match identifier is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        // Consulta la información de las "parejas" del "partido".
        $pairs = array_map(static function (MatchPairPivotModel $matchPairRel): array {
            $pair = $matchPairRel->pair;

            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            return ['pair' => $pair, 'relationship' => $matchPairRel];
        }, $match->matchPairPivot);

        $this->respond($pairs, 'Information about the match pairs');
    }

    /**
     * Modifica la información de la "pareja" en un "partido".
     */
    public function update(string $matchId, string $pairId): void
    {
        // Establece los parámetro de la ruta.
        $params = ['match_id' => $matchId, 'pair_id' => $pairId];

        // Define los campos que se pueden modificar.
        $fields = ['score', 'is_winner'];

        $data = [];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;

            if (is_null($data[$field])) {
                unset($data[$field]);
            }
        }

        $paramNames = array_keys($params);
        $fieldNames = array_keys($data);

        // Obtiene las reglas de validación.
        $rules = MatchPairValidation::getRules(array_merge($paramNames, $fieldNames));

        // Establece los identificadores como obligatorios.
        foreach ($paramNames as $param) {
            array_unshift($rules[$param], 'required');
        }

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Establece los filtros de validación.
        $this->gump()->filter_rules(MatchPairValidation::getFilters($fieldNames));

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run(array_merge($params, $data));

        foreach ($paramNames as $param) {
            unset($data[$param]);
        }

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match pair information is incorrect');
        }

        // Consulta la información de la "pareja" en el "partido".
        $matchPairPivot = new MatchPairPivotModel;
        $matchPairPivot->select('id');

        foreach ($params as $field => $value) {
            $matchPairPivot->eq($field, $value);
        }

        $matchPairPivot->find();

        // Comprueba si existe la "pareja" en el "partido".
        if (! $matchPairPivot->isHydrated()) {
            $this->respondNotFound('The match pair information was not found');
        }

        // Obtiene el ID de la relación de la "pareja" con el "partido".
        $id = $matchPairPivot->id;

        // Modifica la información de la "pareja" en el "partido".
        if (! empty($data)) {
            $matchPairPivot->copyFrom($data);
            $matchPairPivot->update();
            $matchPairPivot->reset();
        }

        // Consulta la información actualizada de la "pareja" en el "partido".
        $matchPairPivot->find($id);

        // Consulta la información del "partido" de la "pareja".
        $match = $matchPairPivot->match;
        $match->setCustomData('registration_category', $match->registrationCategory);
        $match->setCustomData('match_category', $match->matchCategory);
        $match->setCustomData('match_status', $match->matchStatus);

        unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

        // Consulta la información de la "pareja" del "partido".
        $pair = $matchPairPivot->pair;

        // Consulta la "categoría de inscripción" de la "pareja".
        $pair->setCustomData('registration_category', $pair->registrationCategory);
        unset($pair->registration_category_id);

        $this->respondUpdated([
            'match' => $match,
            'pair' => $pair,
            'relationship' => $matchPairPivot,
        ], 'The match pair was updated successfully');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\MatchModel;
use App\Models\MatchPairPivotModel;
use App\Models\PairPlayerPivotModel;
use App\Validations\MatchValidation;

class MatchPairPlayerController extends BaseController
{
    private const LIMIT = 24;

    /**
     * Muestra la información de todas las "parejas" y "jugadores" de los "partidos".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'orderBy' => 'created_at',
            'sortBy' => 'desc',
            'registration_category_id' => null,
            'match_category_id' => null,
            'match_status_id' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(MatchValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(MatchValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The matches pairs search information is incorrect');
        }

        // Consulta la información de todos los "partidos" con paginación.
        $matchModel = new MatchModel;

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'match_category_id', 'match_status_id', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $matchModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $matchModel->paginate($queryParams['page'], self::LIMIT);
        $pagination = $matchModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $matchModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta la información de las "parejas" de los "partido".
        $matches = array_map(static function (MatchModel $match): array {
            $match->setCustomData('registration_category', $match->registrationCategory);
            $match->setCustomData('match_category', $match->matchCategory);
            $match->setCustomData('match_status', $match->matchStatus);

            unset($match->registration_category_id, $match->match_status_id, $match->match_category_id);

            // Consulta la información de las "parejas".
            $pairs = array_map(static function (MatchPairPivotModel $matchPairRel): array {
                $pair = $matchPairRel->pair;

                // Consulta la "categoría de inscripción" de la "pareja".
                $pair->setCustomData('registration_category', $pair->registrationCategory);
                unset($pair->registration_category_id);

                // Consulta la información de los "jugadores" de la "pareja".
                $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                    'player' => $pairPlayerRel->player,
                    'relationship' => $pairPlayerRel,
                ], $pair->pairPlayerPivot));

                return ['pair' => $pair, 'relationship' => $matchPairRel];
            }, $match->matchPairPivot);

            return ['match' => $match, 'pairs' => $pairs];
        }, $matchModel->findAll());

        $this->respondPagination($matches, $pagination, 'Information about the matches pairs and players with pagination');
    }

    /**
     * Muestra la información de las "parejas" y "jugadores" del "partido".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match identifier is incorrect');
        }

        // Consulta la información del "partido".
        $match = new MatchModel;
        $match->select('id')->eq('id', $id)->find();

        // Comprueba si existe el "partido".
        if (! $match->isHydrated()) {
            $this->respondNotFound('The match information was not found');
        }

        // Consulta la información de las "parejas" del "partido".
        $pairs = array_map(static function (MatchPairPivotModel $matchPairRel): array {
            $pair = $matchPairRel->pair;

            // Consulta la "categoría de inscripción" de la "pareja".
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            // Consulta la información de los "jugadores" de la "pareja".
            $pair->setCustomData('players', array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
                'player' => $pairPlayerRel->player,
                'relationship' => $pairPlayerRel,
            ], $pair->pairPlayerPivot));

            return ['pair' => $pair, 'relationship' => $matchPairRel];
        }, $match->matchPairPivot);

        $this->respond($pairs, 'Information about the match pairs with players');
    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\MatchStatusModel;
use App\Validations\MatchStatusValidation;

class MatchStatusController extends BaseController
{
    /**
     * Muestra la información de todos los
     * "estatus de juego de los partidos".
     */
    public function index(): void
    {
        $status = (new MatchStatusModel)->orderBy('description ASC')->findAll();
        $this->respond($status, 'Information about all the matches status');
    }

    /**
     * Muestra la información de un
     * "estatus de juego de los partidos".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = MatchStatusValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The match status identifier is incorrect');
        }

        // Consulta la información del "estatus del partido".
        $matchStatus = new MatchStatusModel;
        $matchStatus->find($id);

        // Comprueba si existe el "status del partido".
        if (! $matchStatus->isHydrated()) {
            $this->respondNotFound('The match status information was not found');
        }

        $this->respond($matchStatus, 'Information about the match status');

    }
}


code:
<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\PairModel;
use App\Models\PairPlayerPivotModel;
use App\Models\PlayerModel;
use App\Validations\PairValidation;
use PDOException;

class PairController extends BaseController
{
    /**
     * Muestra la información de todas las "parejas".
     */
    public function index(): void
    {
        // Define los query params de la petición.
        $queryFields = [
            'page' => 1,
            'orderBy' => 'created_at',
            'sortBy' => 'desc',
            'registration_category_id' => null,
            'is_eliminated' => null,
            'is_active' => null,
        ];

        $queryParams = [];

        // Obtiene solo los query params necesarios.
        foreach ($queryFields as $param => $default) {
            $queryParams[$param] = $this->app()->request()->query->{$param} ?? $default;

            if (is_string($queryParams[$param]) && empty($queryParams[$param])) {
                $queryParams[$param] = $default;
            }

            if (is_null($queryParams[$param])) {
                unset($queryParams[$param]);
            }
        }

        $queryNames = array_keys($queryFields);

        // Obtiene y establece las reglas de validación.
        $this->gump()->validation_rules(PairValidation::getRules($queryNames));

        // Obtiene y establece los filtros de validación.
        $this->gump()->filter_rules(PairValidation::getFilters($queryNames));

        // Comprueba los query params de la petición.
        $queryParams = $this->gump()->run($queryParams);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pairs search information is incorrect');
        }

        // Consulta la información de todas las "parejas" con paginación.
        $pairModel = new PairModel;

        // Establece los filtros permitidos.
        foreach (['registration_category_id', 'is_eliminated', 'is_active'] as $param) {
            if (isset($queryParams[$param])) {
                $pairModel->eq($param, $queryParams[$param]);
            }
        }

        // Obtiene la información sobre la paginación.
        $pairModel->paginate($queryParams['page']);
        $pagination = $pairModel->pagination;

        // Aplica los parámetros de ordenamiento.
        $pairModel->orderBy(sprintf('%s %s', $queryParams['orderBy'], $queryParams['sortBy']));

        // Consulta la "categoría de inscripción" de cada "pareja".
        $pairs = array_map(static function (PairModel $pair): PairModel {
            $pair->setCustomData('registration_category', $pair->registrationCategory);
            unset($pair->registration_category_id);

            return $pair;
        }, $pairModel->findAll());

        $this->respondPagination($pairs, $pagination, 'Information about all the pairs with pagination');
    }

    /**
     * Registra la información de una "pareja".
     */
    public function create(): void
    {
        // Define los campos necesarios de la petición.
        $fields = ['registration_category_id', 'players'];

        $data = [];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;
        }

        // Obtiene las reglas de validación de la "pareja".
        $rules = PairValidation::getRules($fields);

        // Define todas las reglas de validación como obligatorias.
        foreach (array_keys($rules) as $rule) {
            array_unshift($rules[$rule], 'required');
        }

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pair information is incorrect');
        }

        // Comprueba la información de los "jugadores".
        $dataPlayers = (new PlayerModel)->select('id')
            ->in('id', $data['players'])
            ->findAll();

        // Comprueba que los "jugadores" existan.
        if (array_diff($data['players'], array_column($dataPlayers, 'id'))) {
            $this->respondValidationErrors(
                ['players' => 'The players were not found'],
                'The players information is incorrect');
        }

        // Comprueba que los "jugadores" no se repitan.
        if (count($data['players']) !== count($dataPlayers)) {
            $this->respondValidationErrors(
                ['players' => 'The players were repeated'],
                'The players information is incorrect');
        }

        unset($data['players']);

        // Comprueba que los "jugadores" no se encuentren
        // dentro de una "pareja".
        foreach ($dataPlayers as $dataPlayer) {
            if ($dataPlayer->pairPlayerPivot->isHydrated()) {
                $this->respondResourceExists(
                    ['players' => 'The players are already in a pair'],
                    'The players information is incorrect');
            }
        }

        // Registra la información de la "pareja".
        $pair = new PairModel;
        $pair->copyFrom($data);
        $pair->insert();

        $pairPlayerPivot = new PairPlayerPivotModel;

        // Registra la relación del "jugador" con la "pareja".
        foreach ($dataPlayers as $dataPlayer) {
            $pairPlayerPivot->copyFrom(['player_id' => $dataPlayer->id, 'pair_id' => $pair->id]);
            $pairPlayerPivot->insert();
            $pairPlayerPivot->reset();
        }

        $id = $pair->id;

        // Consulta la información de la "pareja" registrada.
        $pair->reset();
        $pair->find($id);

        // Consulta la "categoría de inscripción" de la "pareja".
        $pair->setCustomData('registration_category', $pair->registrationCategory);
        unset($pair->registration_category_id);

        // Consulta los "jugadores" registrados de la "pareja".
        $players = array_map(static fn (PairPlayerPivotModel $pairPlayerRel): array => [
            'player' => $pairPlayerRel->player,
            'relationship' => $pairPlayerRel,
        ], $pair->pairPlayerPivot);

        $this->respondCreated(['pair' => $pair, 'players' => $players], 'The pair was created successfully');
    }

    /**
     * Muestra la información de una "pareja".
     */
    public function show(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = PairValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pair identifier is incorrect');
        }

        // Consulta la información de la "pareja".
        $pair = new PairModel;
        $pair->find($id);

        // Comprueba si existe la "pareja".
        if (! $pair->isHydrated()) {
            $this->respondNotFound('The pair information was not found');
        }

        // Consulta la categoría de inscripción de la "pareja".
        $pair->setCustomData('registration_category', $pair->registrationCategory);
        unset($pair->registration_category_id);

        $this->respond($pair, 'Information about the pair');
    }

    /**
     * Muestra la información del "grupo" de una "pareja".
     */
    public function group(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = PairValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pair identifier is incorrect');
        }

        // Consulta la información de la "pareja".
        $pair = new PairModel;
        $pair->select('id')->eq('id', $id)->find();

        // Comprueba si existe la "pareja".
        if (! $pair->isHydrated()) {
            $this->respondNotFound('The pair information was not found');
        }

        // Consulta la relación de la "pareja" con el "grupo".
        $groupPairRel = $pair->groupPairPivot;

        // Consulta el "grupo" de la "pareja".
        $group = $groupPairRel->_group;

        // Consulta la "categoría de inscripción" del "grupo".
        $group->setCustomData('registration_category', $group->registrationCategory);
        unset($group->registration_category_id);

        $this->respond(['group' => $group, 'relationship' => $groupPairRel], 'Information about the pair group');
    }

    /**
     * Modifica la información de una "pareja".
     */
    public function update(string $id): void
    {
        // Define los campos que se pueden modificar.
        $fields = ['is_eliminated', 'is_active'];

        $data = ['id' => $id];

        // Obtiene solo los campos necesarios.
        foreach ($fields as $field) {
            $data[$field] = $this->app()->request()->data->{$field} ?? null;

            if (is_null($data[$field])) {
                unset($data[$field]);
            }
        }

        $fieldNames = array_keys($data);

        // Obtiene las reglas de validación
        // y establece el identificador como obligatorio.
        $rules = PairValidation::getRules(['id', ...$fieldNames]);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Establece los filtros de validación.
        $this->gump()->filter_rules(PairValidation::getFilters($fieldNames));

        // Valida el cuerpo de la petición.
        $data = $this->gump()->run($data);

        unset($data['id']);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pair information is incorrect');
        }

        // Consulta la información la "pareja".
        $pair = new PairModel;
        $pair->select('id')->eq('id', $id)->find();

        // Comprueba si existe la "pareja".
        if (! $pair->isHydrated()) {
            $this->respondNotFound('The pair information was not found');
        }

        // Modifica la información de la "pareja".
        if (! empty($data)) {
            $pair->copyFrom($data);
            $pair->update();
            $pair->reset();
        }

        // Consulta la información actualizada de la "pareja".
        $pair->find($id);

        // Consulta la "categoría de inscripción" de la "pareja".
        $pair->setCustomData('registration_category', $pair->registrationCategory);
        unset($pair->registration_category_id);

        $this->respondUpdated($pair, 'The pair was updated successfully');
    }

    /**
     * Elimina la información de una "pareja".
     */
    public function delete(string $id): void
    {
        // Obtiene las reglas de validación
        // y las establece como obligatorias.
        $rules = PairValidation::getRules(['id']);
        array_unshift($rules['id'], 'required');

        // Establece las reglas de validación.
        $this->gump()->validation_rules($rules);

        // Comprueba los parámetros de la petición.
        $this->gump()->run(['id' => $id]);

        // Comprueba si existen errores de validación.
        if ($this->gump()->errors()) {
            $this->respondValidationErrors(
                $this->gump()->get_errors_array(),
                'The pair identifier is incorrect');
        }

        // Consulta la información de la "pareja".
        $pair = new PairModel;
        $pair->find($id);

        // Comprueba si existe la "pareja".
        if (! $pair->isHydrated()) {
            $this->respondNotFound('The pair information was not found');
        }

        // Consulta la categoría de inscripción de la "pareja".
        $pair->setCustomData('registration_category', $pair->registrationCategory);
        unset($pair->registration_category_id);

        // Elimina la información de la "pareja".
        try {
            $pair->delete();
        } catch (PDOException $e) {
            $this->respondConflict('The pair contains related information');
        }

        $this->respondDeleted($pair, 'The pair was deleted successfully');
    }
}