Despiece
Tutorial · Lineales (1D · listones, perfiles y varillas)

TutorialLineales · 1D listones, perfiles y varillas

Tutorial paso a paso del módulo Lineales de Despiece: el toggle de activación en Configuración → Lineales, los defaults del taller (kerf, espesor de disco, perfil y material), el editor de keywords para auto-detectar cortes a partir de los nombres del CAD, el panel [L] con sus vistas Stock | Catálogo, el wizard + Listón, el importador CSV bilingüe (Despiece + OptiCut), el packer FFD 1D con multi-restart y la integración con BOM · Pedido · Etiquetas.

Las lineales son piezas 1D (no tableros 2D): listones de madera maciza, machimbres, perfiles de aluminio, zócalos PVC, varillas de placard Ø25/30, tirantes, alfajías y tapacantos. Se cortan de barras nominales del catálogo y se empacan con un bin-packer FFD + multi-restart (primer ajuste decreciente con 32 variantes semilladas). El módulo está desactivado por default para no pagar el costo del UI extra en apps que sólo trabajan con tableros crudos. La activación se persiste en state.settings.linealesEnabled y sobrevive a .despiece recargados: los datos lineales se guardan como soft fields (linearItems, linearResult) y se recuperan al reactivar. El modelo de ejemplo es CocinaV9.despiece.

1 · Activar el módulo Lineales

Settings → Lineales con el toggle Activar módulo apagado
Figura 01 · Settings → Lineales, tab con el toggle Activar módulo apagado por default.

El módulo vive detrás de un toggle global porque las apps que sólo trabajan con tableros crudos no necesitan el UI extra. La activación se hace una sola vez por taller.

Configuración (icono engranaje de la topbar) → tab Listones (cortado 1D) (id lineal, registrado en src/ui/settings-panel/tabs.ts:26). El primer row, Activar módulo Lineales (#stg-lineales-enabled, persistido vía setSettings({ linealesEnabled }) desde src/ui/settings-panel/lineal-wiring.ts:34) prende o apaga todo lo demás.

Cuando el toggle está apagado:

SuperficieEstado
Botón [L] del topbar (#tb-lineales)hidden (no se renderiza)
Atajo Shift+LNo-op + toast "Módulo Lineales desactivado · activalo en Settings → Lineales"
Botón [L] del menú Exportar (4 items)hidden vía state-apply.ts:146-166
Items linearItems guardados en discoSe conservan tal cual — al reactivar se recuperan
Resultado linearResult previoSe conserva; al reactivar se re-puede abrir

Esto significa que podés apagar el módulo para limpiar la UI sin perder trabajo: las marcas quedan en el .despiece y se reactivan al volver a prender.

2 · Settings → Lineales · defaults del taller

Defaults del taller: kerf, blade, perfil y material por defecto
Figura 02 · Los 4 inputs de defaults (kerf, espesor de disco, perfil, material).

El tab tiene cuatro inputs numéricos / texto que aplican tanto al wizard + Listón como al botón Calcular barras (FFP) y al botón Auto-detectar del modelo. Todos se persisten en localStorage[despiece.lineales.defaults.v1] y se sanitizan con sanitizeLinearDefaults() (cae a per-field default si el valor está corrupto, nunca tira):

Campo Selector Default Rango Función
Kerf [data-lineal="kerfMm"] 3 mm 0 – 20 (step 0.5) Separación entre cortes consecutivos en la misma barra. El packer lo aplica con kerfMm = 0 en el primer corte de cada barra y suma kerfMm entre cortes del mismo slot.
Espesor de disco [data-lineal="bladeThicknessMm"] 3 mm 0 – 10 (step 0.5) Informativo; el packer no lo usa. Reservado para cutter-compensation futuro.
Perfil por defecto [data-lineal="defaultProfileId"] pino-2x3 id del catálogo ID del LinearStockProfile que arranca el wizard (el selector cambia a este perfil).
Material por defecto [data-lineal="defaultMaterialId"] maciza_pino_fk id del catálogo Material que se asigna al item recién creado si el perfil no lo trae.

El packer hardcodea allowRotate: false (DEFAULT_LINEAL_DEFAULTS y types.ts:125). Las lineales no rotan: el corte siempre se mide sobre el largo nominal de la barra (spec.lengthMm). Esto es por dominio — una varilla de placard Ø25 sigue siendo Ø25 en cualquier posición, pero el packer no intenta cortar transversal.

3 · Auto-detección por nombre (keywords)

Editor CRUD de keywords de auto-detect
Figura 03 · Lista de 14 keywords built-in + form + Agregar + Restablecer por defecto.

El botón Auto-detectar del modelo del panel [L] usa un diccionario de palabras clave para proponer piezas como lineales a partir del nombre del CAD (sin mutar el modelo — solo agrega items a state.project.linearItems). La regla es longest-keyword-wins: si un part se llama Estante listonado reforzado, matchea listonado (9 chars) por encima de listón (6) o estante (7).

El editor (#stg-lineales-keywords-list) tiene tres filas:

FilaFunción
Lista de keywords (existente) Cada row expone palabra + perfil editable + × para borrar. Cambiar el perfil persiste sin re-render completo.
Form + Agregar Input palabra + select perfil + botón. Trim + lowercase en el key (ver sanitizeLinearKeywords).
Restablecer por defecto Vuelve al set built-in (resetLinearKeywords).

El matcher (matchLinealKeyword en src/lineales/auto-lineales.ts:38) combina folding de acentos (Listónliston) con token-AND para multi-token: si el keyword es tirante pine, todos sus tokens tienen que aparecer como tokens del nombre (en cualquier orden).

Keywords built-in (src/lineales/types.ts:168):

Keyword Perfil sugerido Uso típico
listón · listones · listonpino-2x3Listones de pino genéricos.
machimbremachimbre-1x4Machimbre 1×4 para cielorrasos / frisos.
perfilaluminio-l-1Perfiles de aluminio en L.
zócalo · zocalopvc-zocalo-100Zócalos PVC 100 mm.
varilla · varilla 25 · varilla 30varilla-placard-25 / varilla-placard-30Varillas de placard.
tirante · alfajía · alfajiapino-2x3Tirantes y alfajías de techo.
tapacantoaluminio-tapacanto-22Tapacantos de aluminio.

Cada keyword tiene también aliases localizados en aliasesByLocale (es-AR, en-US, pt-BR, fr-FR, de-DE, it-IT) para que varilla matchee también closet rod / cabideiro / tringle penderie / Kleiderstange / asta armadio. El matching sigue siendo longest-key + folding; los aliases viven en los perfiles, no en las keywords.

4 · Catálogo built-in (vista Catálogo del panel)

Catálogo de los 11 perfiles built-in
Figura 04 · Vista Catálogo del panel con los 11 perfiles built-in (read-only).

El panel [L] tiene una vista Catálogo (segmented al lado de Stock) que muestra los perfiles disponibles en modo read-only. La edición se hace en Configuración → Materiales (customs) o desde el catálogo lineal (src/lineales/stock-store.ts).

Los 11 perfiles built-in viven en src/lineales/types.ts:136-146:

ID Familia Etiqueta Perfil Largo nominal Material
pino-2x2maderaPino FK 2×2″50×50 mm2400 mmmaciza_pino_fk
pino-2x3maderaPino FK 2×3″50×75 mm2400 mmmaciza_pino_fk
pino-3x3maderaPino FK 3×3″75×75 mm2400 mmmaciza_pino_fk
eucalipto-2x3maderaEucalipto 2×3″50×75 mm2400 mmmaciza_eucalipto
paraiso-2x3maderaParaíso 2×3″50×75 mm2400 mmmaciza_paraiso
machimbre-1x4machimbreMachimbre 1×4″25×100 mm2400 mmmaciza_machimbre
aluminio-l-1aluminioAluminio L 1″25×25 mm3000 mmaluminio_perfil
aluminio-tapacanto-22aluminioTapacanto aluminio 22 mm22×600 mm3000 mmaluminio_tapacanto
pvc-zocalo-100pvcZócalo PVC 100 mm100×15 mm2400 mmpvc_zocalo
varilla-placard-25otroVarilla placard Ø2525×25 mm2400 mmvarilla_placard
varilla-placard-30otroVarilla placard Ø3030×30 mm2400 mmvarilla_placard

Los built-ins son inamovibles — viven en el código, no en localStorage. Si necesitás un perfil custom (ej. Pino tratado 2×4″), agregalo vía addLinealProfile desde el panel de stock catalogado (cap 200 customs en stock-store.ts:13).

5 · Abrir el panel [L] (Shift+L)

Panel Lineales vacío, vista Stock con wizard
Figura 05 · Panel [L] recién abierto, vista Stock con el wizard + Listón.

Cargar un modelo CAD (cualquier STEP/IGES/BREP/glTF/GLB), pulsar Shift+L o click en el botón [L] del topbar (id #tb-lineales). El panel se abre como un sheet full-screen (min(1100px, 92vw) × 86vh) con un segmented Stock | Catálogo.

El shell vive en src/ui/lineales-panel.ts. La vista por default es Stock; el switcher es el nav.lineales-seg con dos botones .lineales-seg-btn[data-view="stock|catalogo"]. El header expone:

Si el módulo está apagado en Settings, el botón [L] no se renderiza y el atajo Shift+L muestra un transient:

Módulo Lineales desactivado · activalo en Settings → Lineales

6 · + Listón · wizard manual

Wizard + Listón con nombre, perfil, largo y cantidad
Figura 06 · Wizard + Listón con los 4 campos llenos antes de Agregar.

El título del wizard es + Listón y su botón de submit (#lineales-add, etiqueta "Agregar") toma cuatro campos del #lineales-w-* form. La validación (stock-view.ts:70):

if (!profile || !Number.isFinite(length) || length <= 0
    || !Number.isFinite(qty) || qty <= 0) return;
Campo Selector Validación
Nombre #lineales-w-name trim, no vacío; si está vacío cae al profileId.
Perfil .herraje-select[data-field="lineales-w-profile"] id del catálogo (11 built-in + customs). Default = defaultProfileId de Settings.
Largo (mm) #lineales-w-length número entero > 0. Math.round(length) antes de guardar.
Cantidad #lineales-w-qty entero ≥ 1. Math.floor(qty) antes de guardar.

Al click Agregar se llama addLinearItem({...source: 'wizard'}) en src/state/lineales-slice.ts:22. La función genera id = uuid() si falta, valida con isLinearStockLine (defensa contra shape inválido), y bumpea linearEpoch (la vista Stock se re-renderiza vía on(state)).

Internamente el item nuevo es un LinearStockLine:

interface LinearStockLine {
  id: string;
  parentPartId?: string;
  name: string;
  profileMm: { w: number; h: number };  // snapshot del perfil
  lengthMm: number;                      // largo del corte pedido
  materialId: string;                    // snapshot del material
  qty: number;
  source: 'cad' | 'wizard' | 'csv' | 'auto-detect';
}

El item no clona el perfil — copia profileMm por valor y referencia materialId por id. Si después se renombra el material en Configuración → Materiales, materials/rewrite-refs.ts:124 reescribe el materialId en todos los lineales al cargar el proyecto (consistencia con el resto de la app).

7 · Importar CSV (Despiece-lineal y OptiCut-lineal)

Lista con cuatro items: pino cajón, refuerzo, machimbre y varilla
Figura 07 · Lista con 4 items de stock tras varios Agregar (perfiles y largos distintos).

El botón Importar CSV… (#lineales-import-csv) abre un picker nativo y acepta .csv / .tsv. El parser (src/lineales/parse-csv.ts) detecta dos dialectos mirando el header (case-insensitive, lowercase) y maneja BOM UTF-8:

Dialecto Header Detección
despiece-lineal cantidad;nombre;profile_mm;largo_mm;material header contiene profile_mm o largo_mm
opticut-lineal quantità;descrizione;lunghezza_mm;materiale header contiene descrizione o lunghezza
unknown (cualquier otro) igual parsea con alias genéricos si las columnas matchean

El separador también se autodetecta (detectSep línea 37): si el header tiene ;, sep = ;, si no ,. La columna profile_mm acepta , o . como decimal y x o × como separador, vía regex ^(\d+(?:[.,]\d+)?)\s*[x×]\s*(\d+(?:[.,]\d+)?)$/i (línea 57).

Ejemplo válido (/tmp/lineales-demo.csv):

cantidad;nombre;profile_mm;largo_mm;material
4;Listón pino 2×3 refuerzo;50×75;1800;maciza_pino_fk
2;Machimbre 1×4 cielorraso;25×100;2200;maciza_machimbre
8;Varilla placard 25 closet;25×25;1200;varilla_placard

Cada fila se agrega con addLinearItem({..., source: 'csv'}). Las filas inválidas (qty ≤ 0, profile sin match, length no numérico) van a errors[] y no rompen el parse — el resto del archivo entra.

CampoAliases de columnas (lowercase)
qtycant, qty, quantità, quantita
namenombre, name, descrizione, desc
profileprofile_mm, profile, profilo_mm, profilo
lengthlargo_mm, largo, length_mm, lunghezza_mm, lunghezza

Si falta alguna columna obligatoria, el parser devuelve errors: ['Faltan columnas: cant/qty, nombre/name, profile_mm, largo_mm'] y no agrega nada. La columna material del CSV es opcional — si está vacía, el item se guarda con materialId = 'maciza_pino_fk' (fallback de pino FK para no romper el lookup downstream).

8 · Auto-detectar del modelo

Estado tras Auto-detectar (sin matches nuevos en CocinaV9)
Figura 08 · Vista Stock tras Auto-detectar del modelo — CocinaV9 no matchea ninguna keyword.

El botón Auto-detectar del modelo (#lineales-auto) corre selectLinealMatches(parts, keywords) y agrega un item por cada match con source: 'auto-detect'. La pieza original del CAD no se muta — solo se crea un LinearStockLine con parentPartId: m.part.id y lengthMm = Math.round(max(profile.nominalLengthMm, 100) / 4) (un cuarto del largo nominal como heurística de corte inicial).

El cuarto de largo nominal es solo un starting point — el usuario edita el lengthMm directamente en la fila de stock. Para CocinaV9 el catálogo de nombres es estándar (puerta, estante, cajón) y el auto-detect no agrega items porque ninguno matchea las keywords.

El flag addLinearItem bumpea linearEpoch; la vista Stock se re-renderiza mostrando los items nuevos. Si la pieza ya tenía un item lineal previo (mismo parentPartId), el nuevo se acumula — no se deduplica (decisión del spec §3.13: auto-detect es sugerir, no sincronizar).

9 · Calcular barras (packer FFD 1D + multi-restart)

Workbench con bandas apiladas y cortes cobalt + remanente tramado
Figura 09 · Workbench con bandas horizontales apiladas: cada barra es un track, los cortes cobalt, el remanente tramado.

El botón Calcular barras (#lineales-pack) corre packLinealStock(items, spec) (src/lineales/packer.ts:164) sobre la lista actual. El algoritmo es:

  1. Expand cada LinearStockLine por su qty → flat array de ExpandedCut[] (línea 147).
  2. FFD inicial = sort((a,b) => b.lengthMm - a.lengthMm) — primer ajuste decreciente (línea 171).
  3. Multi-restart hasta 32 variantes o 250 ms. La RNG es makeRng (Mulberry32-like en packer.ts:51) y el barajado se aplica con shuffleSeeded (packer.ts:61).
  4. Greedy placement (placeRuns línea 77): mantiene un slot[] por barra viva con {barId, nextStart}; para cada cut busca el primer slot donde nextStart + cut.lengthMm + kerfMm ≤ spec.lengthMm. Si no entra, abre una barra nueva con kerf = 0 en el primer cut.
  5. Best of N — elige la variante con menor waste total; desempata por menor totalBars (líneas 192-201).
  6. Output inmutable: LinearResult con bars, cuts, unusedByBar, wasteMmByBar, totalMl, totalBars, cfg.

El spec.bar se construye a partir del primer item: profileId + lengthMm = profile.nominalLengthMm (largo nominal del catálogo). Todos los items del pack deben compartir perfil — si mezclás listones de 50×75 con varillas de 25×25, el packer aplica el perfil del primero a todos (con un warning por consola si hay mismatch).

Workbench (src/ui/lineales-panel/workbench.ts):

El remanente está representado visualmente pero no se trackea como remnantOfBarId en el output — solo queda en unusedByBar[barId] como {startMm, lengthMm} para futuro uso (reutilización de retazos entre obras). El spec §3.10 lo marca como pendiente.

10 · Exportar (CSV · XLSX · PDF) + atajos

Menú Exportar con CSV, XLSX y PDF
Figura 10 · Menú Exportar del header con CSV / XLSX / PDF.

El botón Exportar ([data-action="export-toggle"]) del header abre un popover con tres opciones. Cada handler vive en src/export/actions-linear.ts y se cablea vía setLinealesHandlers({ onExport }) en src/ui/lineales-panel.ts:33:

Formato Columnas / output Notas
CSV cantidad;nombre;profile_mm;largo_mm;material;ml UTF-8 BOM + CRLF. Sin header en TSV de corte. ml = lengthMm × qty / 1000.
XLSX Segunda hoja "Lineales" del BOM exceljs dynamic import (no entra al bundle inicial).
PDF Etiquetas de barras Toggle linearLabelsBreakdown decide si es 1 etiqueta por barra con desglose de cortes, o 1 por corte.

El export respeta state.settings.exportVisibleOnly igual que el resto de la app (default true). Si el módulo está apagado, los 4 items del menú Export se ocultan vía src/ui/topbar/sections/state-apply.ts:146-166.

Atajos del flujo

Hoja de atajos con el módulo Lineales listado
Figura 11 · Hoja de atajos con la card Lineales (1D) y el atajo Shift+L.
AtajoAcción
Shift+LAbrir/cerrar el panel [L] (Mac = metaKey, otros = ctrlKey)
Esc dentro del panelCerrar el sheet (vuelve al viewport)
? / F1Abrir la hoja de atajos completa (incluye el link a este tutorial)
+E / Ctrl+EMenú Exportar (CSV / XLSX / PDF · BOM · Pedido · Etiquetas · Listones CSV/XLSX/PDF)
F / HomeFit / Fit + iso del viewport 3D

El panel [L] no captura el atajo L simple (reservado para futuro List view del sidebar). El modificador Shift lo desambigua y deja el shortcut simple libre.

Pipeline integrado

El LinearResult se cablea hacia abajo en cuatro destinos:

Downstream Comportamiento Módulo
BOM CSV / XLSX / PDF buildLinearBomRows agrega las filas de lineales con cantidad;nombre;profile_mm;largo_mm;material;ml. En XLSX es la segunda hoja Lineales. src/export-bom/build-bom-rows.ts
Pedido de materiales buildLinearPurchaseRows suma costPerBar × totalBars por perfil × material. Aparece como bloque Barras lineales al final del PDF/CSV de pedido. src/export-purchase/linear-purchase-rows.ts:17
Etiquetas PDF export-labels.ts chequea linearLabelsBreakdown; cuando está activado, una etiqueta por barra con desglose de cortes pegados; cuando está apagado, una etiqueta por corte. src/export-labels.ts
Total de obra El panel [L] muestra ml lineal en el header (suma de lengthMm × qty / 1000). El multiplicador ×N aplica antes del pack (clamp 1–99). recalculateIfResult()

Multiplicador de obra ×N: si state.settings.projectMultiplier = 3 y tenés 4 listones ×2400, el packer recibe 12 items en lugar de 4 (no clona meshes 3D — las lineales no tienen). El contador de barras se recalcula automáticamente vía recalculateIfResult() (lineales-panel.ts:174), que re-corre el pack si hay linearResult previo en disco.

En Mac el modificador de sistema es ; en otros sistemas Ctrl.


Ver también