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 esNULL, 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 horariaAmerica/Mexico_City, un color hexadecimal#10B981o 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}");
}
}
}