resources/views/pages/frontend/docs/catalogos.md
Última modificación: 24 Oct, 2026

Catálogos Recursivos & Diccionario Core

El sistema de catálogos de Mañuti constituye la columna vertebral para la estandarización y normalización semántica de toda la base de datos relacional. En lugar de crear decenas de tablas pequeñas independientes (e.g., estatus_usuarios, tipos_documentos, zonas_horarias), el framework implementa una Tabla Maestra Recursiva bajo el patrón de lista de adyacencia (Adjacency List Model).


1. Estructura Relacional de la Tabla catalogos

La tabla se autoreferencia a través de la llave foránea catalogo_id, permitiendo una jerarquía infinita de niveles (Agrupadores Padre $\rightarrow$ Opciones Hijas $\rightarrow$ Sub-opciones):

CREATE TABLE `catalogos` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `catalogo_id` BIGINT UNSIGNED NULL,
    `catalogo` VARCHAR(255) NOT NULL,
    `etiqueta` VARCHAR(255) NOT NULL,
    `activo` VARCHAR(5) DEFAULT 'true',
    `orden` INT DEFAULT 0,
    `valor` VARCHAR(255) NULL,
    `created_at` TIMESTAMP NULL,
    `updated_at` TIMESTAMP NULL,
    `deleted_at` TIMESTAMP NULL,
    FOREIGN KEY (`catalogo_id`) REFERENCES `catalogos`(`id`) ON DELETE CASCADE
);

CREATE INDEX `catalogos_catalogo_index` ON `catalogos`(`catalogo`);
CREATE INDEX `catalogos_catalogo_id_index` ON `catalogos`(`catalogo_id`);

Campos del Diccionario

  • catalogo_id (FK): Apunta al nodo padre. Si es NULL, el registro es una Categoría Raíz (e.g., SYS_ESTATUS_USUARIO, SYS_TIPO_DOC). Si contiene un ID, es una Opción Seleccionable.
  • catalogo (Slug): Clave alfanumérica única en mayúsculas (USR_ACTIVO, DOC_PUBLICADO). Es el token inmutable utilizado por desarrolladores en código, controladores y seeders.
  • etiqueta: Texto descriptivo visible para el usuario final en la interfaz gráfica (Activo, Publicado (Vigente)).
  • activo: Bandera lógica ('true'/'false') que permite apagar opciones en desuso sin romper la integridad referencial histórica.
  • orden: Secuencia numérica entera para controlar el ordenamiento en listas desplegables.
  • valor: Metadato técnico opcional (e.g., el código IANA de una zona horaria America/Mexico_City, un color hexadecimal #10B981 o una abreviatura ISO).

2. Convención Semántica de Llaves Foráneas (cat_)

Para evitar la ambigüedad generada por la convención nativa de Laravel (catalogo_id), Mañuti estandariza el uso del prefijo semántico cat_ seguido del contexto de negocio:

// database/migrations/xxxx_create_clientes_table.php
Schema::create('clientes', function (Blueprint $table) {
    $table->id();
    $table->string('nombre');

    // Múltiples llaves apuntando a la misma tabla 'catalogos'
    $table->unsignedBigInteger('cat_status');
    $table->unsignedBigInteger('cat_tipo_cliente');
    $table->unsignedBigInteger('cat_zona_facturacion');

    $table->foreign('cat_status')->references('id')->on('catalogos');
    $table->foreign('cat_tipo_cliente')->references('id')->on('catalogos');
    $table->foreign('cat_zona_facturacion')->references('id')->on('catalogos');
});

3. Abstracción en Modelos Eloquent (App\Models\Catalogo)

El modelo resuelve la recursividad mediante relaciones reflexivas e incorpora un helper estático de alto rendimiento protegido por Redis:

// app/Models/Catalogo.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Cache;

class Catalogo extends Model
{
    use SoftDeletes;

    protected $table = 'catalogos';
    protected $fillable = ['catalogo_id', 'catalogo', 'etiqueta', 'activo', 'orden', 'valor'];

    public function hijos(): HasMany
    {
        return $this->hasMany(Catalogo::class, 'catalogo_id');
    }

    public function padre(): BelongsTo
    {
        return $this->belongsTo(Catalogo::class, 'catalogo_id');
    }

    /**
     * Recupera opciones activas en memoria RAM de Redis
     */
    public static function getOpciones(string $slugPadre)
    {
        return Cache::remember("cat_slug_{$slugPadre}", 86400, function () use ($slugPadre) {
            $padre = self::where('catalogo', $slugPadre)->first();
            if (!$padre) return collect();

            return $padre->hijos()
                ->where('activo', 'true')
                ->orderBy('orden', 'asc')
                ->get(['id', 'catalogo', 'etiqueta', 'valor']);
        });
    }
}

En las entidades consumidoras (e.g., Usuario, Documento), la relación se mapea explícitamente:

public function estatus(): BelongsTo
{
    return $this->belongsTo(Catalogo::class, 'cat_status', 'id');
}

4. Invalidación de Caché en Caliente

Cuando un operador actualiza, reordena o desactiva un registro en /catalogos, el controlador ejecuta la invalidación quirúrgica de las llaves en Redis:

// app/Http/Controllers/Admin/CatalogoController.php
private function limpiarCache(?int $parentId): void
{
    if ($parentId) {
        $padre = Catalogo::find($parentId);
        if ($padre) {
            Cache::forget("cat_slug_{$padre->catalogo}");
        }
    }
}

Enlaces Relacionados

Edita este documento en >> catalogos.md
← Volver a la Landing Page