Utils
utils/README.md
Utils
Funciones utilitarias puras agrupadas por dominio. No tienen estado, no producen side-effects y no dependen de React. Pueden usarse tanto en hooks como en componentes.
Descripción General
utils/
├── categories/
│ ├── build-catalog.ts # Transforma ICategoryCatalog[] → IGroupCatalogView[]
│ └── icon-map.ts # Resuelve nombre de ícono → componente LucideIcon
├── materials/
│ ├── build-material-rows.ts # Convierte IMaterial[] + catálogo → IMaterialRow[]
│ └── resolve-categorization.ts # Resuelve la jerarquía completa a partir de un subcategoryId
└── shared/
├── text-utils.ts # normalizeText / matchesSearch
└── utils.ts # cn() — classnames + tailwind-merge
🗂️ utils/categories
buildGroupCatalogViews(catalog)
Archivo: utils/categories/build-catalog.ts
Transforma el resultado de getCategoryCatalog() (forma de Firestore) en el modelo de vista que usan los componentes de UI.
| Parámetro | Tipo | Descripción |
|---|---|---|
catalog | ICategoryCatalog[] | Array de grupos con sus categorías y subcategorías |
Retorna: IGroupCatalogView[]
buildGroupCatalogViews(catalog);
// Input (Firestore shape)
// { group: IGroup, categories: ICategory[], subcategories: ISubcategory[] }
// Output (View shape)
// { id, title, description, iconName, categories: [{ id, name, subcategories: [{ id, name, materialsCount }] }] }
Nota:
materialsCountse resuelve desde Firestore cuando el UI usahooks/categories/use-material-counts.ts.
getGroupIcon(iconName)
Archivo: utils/categories/icon-map.ts
Resuelve un nombre de ícono (string key de CONSTRUCTION_ICON_OPTIONS) al componente LucideIcon correspondiente. Si no encuentra coincidencia, retorna el primer ícono disponible como fallback.
| Parámetro | Tipo | Descripción |
|---|---|---|
iconName | string | Clave del ícono (ej. 'brick-wall') |
Retorna: LucideIcon
import { getGroupIcon } from '@/utils/categories/icon-map';
const Icon = getGroupIcon('paint-bucket'); // → PaintBucket
� utils/materials
buildSubcategoryLookup(catalog)
Archivo: utils/materials/build-material-rows.ts
Construye un Map<subcategoryId, label> recorriendo el catálogo completo. El label tiene la forma "Rubro / Categoría / Subcategoría" y se usa para mostrar la categorización en tablas.
buildMaterialRows(materials, lookup)
Archivo: utils/materials/build-material-rows.ts
Transforma un array de IMaterial (forma de Firestore) en el modelo de vista IMaterialRow que consumen los componentes de tabla. Incorpora groupId, categoryId y subcategoryId para el filtrado en cliente.
| Parámetro | Tipo | Descripción |
|---|---|---|
materials | IMaterial[] | Array de materiales crudos de Firestore |
lookup | Map<string, string> | Mapa generado por buildSubcategoryLookup |
Retorna: IMaterialRow[]
resolveFullCategorization(subcategoryId, catalog)
Archivo: utils/materials/resolve-categorization.ts
Navega el catálogo completo buscando la subcategoría por ID y retorna todos los campos de la jerarquía necesarios para persistir un material. Se usa en los hooks de diálogo y en la importación masiva para enriquecer el payload antes de guardarlo.
| Parámetro | Tipo | Descripción |
|---|---|---|
subcategoryId | string | ID de la subcategoría |
catalog | ICategoryCatalog[] | Catálogo completo cargado |
Retorna: IFullCategorization
interface IFullCategorization {
categoryId: string;
groupId: string;
groupName: string;
categoryName: string;
subcategoryName: string;
label: string; // "Rubro > Categoría > Subcategoría"
isValid: boolean; // false si el subcategoryId no existe en el catálogo
}
resolveCategorizationById(subcategoryId, catalog)
Archivo: utils/materials/resolve-categorization.ts
Atajo que retorna solo { label, isValid } a partir de resolveFullCategorization. Útil para resolver el texto de categorización cuando no se necesitan los IDs individuales.
resolveCategorizationFromRawLabel(rawLabel, catalog)
Archivo: utils/materials/resolve-categorization.ts
Extrae el subcategoryId de un texto con el formato "Cualquier texto [ID: <id>]" (generado por la plantilla de importación Excel) y luego llama a resolveCategorizationById. Se usa en el flujo de importación masiva de materiales.
🔧 utils/shared
normalizeText(text)
Archivo: utils/shared/text-utils.ts
Normaliza un string para búsquedas: elimina diacríticos (acentos), convierte a minúsculas y recorta espacios. Internamente usa String.normalize('NFD') + regex Unicode para remover las marcas diacríticas.
normalizeText('Cémento Portland'); // → 'cemento portland'
normalizeText('MAÑERÍA'); // → 'mañeria' (no elimina la ñ, solo los diacríticos de acento)
Usado en: búsqueda en useMaterialCatalogPage, useCategoriesPage, useCategoryCombobox, y en el Route Handler GET /api/materials.
matchesSearch(text, search)
Archivo: utils/shared/text-utils.ts
Devuelve true si text normalizado contiene search normalizado. Envuelve normalizeText para uso directo en filtros de array.
matchesSearch('Cemento Portland 50kg', 'portland'); // → true
matchesSearch('Ladrillo común', 'ladrillos'); // → false
cn(...inputs)
Archivo: utils/shared/utils.ts
Wrapper sobre clsx + tailwind-merge. Combina clases condicionales y resuelve conflictos de Tailwind.
import { cn } from '@/utils/shared/utils';
<div className={cn('base-class', isActive && 'active-class', className)} />
Cómo Agregar Utilidades
- Si es específica de un dominio, crear en
utils/<dominio>/nombre.ts. - Si es genérica, agregar en
utils/shared/utils.tso en un archivo nuevo dentro deutils/shared/. - Las funciones deben ser puras: mismo input → mismo output, sin efectos secundarios.
- Documentar el tipo de input y output con TypeScript — no con comentarios.
