# Tutorial · Optimización de corte (nesting)

Tutorial paso a paso del módulo de **nesting** de Despiece: el solver
que empaqueta tus piezas en tableros stock minimizando desperdicio,
con ciclo de tiempo estimado, secuencia de corte tipo TSP y exports
listos para la sierra (PDF), la CNC (DXF), etiquetas por tablero y
piezas 2D para outsource. La experiencia está enmarcada como
**propuesta de taller** (no optimizador industrial) y suma un flujo
**por módulo** para mantener el orden de armado a través de los
tableros.

El modelo de ejemplo es `CocinaV9.despiece`.

---

## Novedades 2026-08 (vs v1)

| Feature | Dónde | Spec / commit |
|---------|-------|---------------|
| **Diagrama cut-man** | Workbench + PDF | `b47dfa9` (cut #s + edge legend + readable labels) |
| **Optimizar por módulo** (A1 flujo) | Setup ▸ *Optimizar para* | `6305191`, `b79516e`, `52394b1` |
| **Tablero auto (phantom)** | Setup ▸ banner | `642c310`, `c50c9d5`, `3a5273d` |
| **Compra sugerida CSV** | Toolbar ▸ *Compra sugerida* | `b4d6188` |
| **Guardar phantom → stock** | Banner expandido | `dcf78b3` |
| **Propuesta de taller** | Toolbar + chips | `a06fe52` |
| **Etiquetas por tablero** | Workbench ▸ *Etiquetas* (≥2 tableros) | `39ffc31` |
| **Piezas 2D (.zip DXF)** | Exportar ▸ *Piezas 2D* (no desde el nest) | `95f4ce5` |

---

## 1 · Cargar el modelo y abrir el panel

![Vista general con el botón de nesting en la topbar](img/01-overview-corte.jpg)

Al importar el STEP/IGES/BREP/glTF/GLB, la sidebar izquierda muestra el
árbol jerárquico. Para arrancar el corte el atajo es `N`, que abre el
panel modal full-screen (`nest-root`) en modo **Setup**.

La cámara vuelve a vista isométrica con `Home`; el zoom encaja con `F`.

## 2 · Setup: stock + sierra

![Modo Setup del panel de nesting](img/02-setup-corte.jpg)

El Setup tiene dos columnas. La columna izquierda carga el stock del
taller (presets + retazos del inventario) y la derecha opera la sierra
y los knobs del solver:

- **Tableros stock** — presets del taller (Melamina 18 / 15 / MDF,
  OSB, Fenólico, 1830×2750 / 5600, etc.) + retazos entre obras.
  Activá los que querés usar; el solver solo trabaja con los
  activados y los filtra por espesor / material. Si no cargás nada,
  el solver **genera tableros auto (phantom)** igual (ver §9).
- **Sierra** — knobs operator-facing:
  - `kerf` (mm, 0–10, step 0.5; default 3 mm).
  - `Recorte de fábrica` = `borderMargin` (mm, 0–50; default 0).
  - `Permitir rotación 90°` — si la pieza admite girar (no respeta
    veta).
  - `1ª pasada` — H o V; sesgo leve en el ordering del solucionador.
  - **`Optimizar para`** — `Material` / `Tiempo` / `Mixto` /
    **`Por módulo`** (ver §7).
- **Avanzado (solver)** — acordeón colapsable con `algoritmo`
  (auto / guillotine / maxrects), `restarts` (1–20, default 5),
  `time budget` (1–30 s, default 5), `seed` (0 = random).

> Si tu modelo no tiene `treePath` o tenés un solo módulo, la cola de
> módulos aparece vacía — Despiece cae al comportamiento clásico
> (optimizar para material/tiempo) y la fila *Sin módulo* se ignora.

El botón **Calcular** dispara `packNestMulti` y, cuando termina,
transiciona al modo **Workbench**.

## 3 · Calcular y entrar al workbench (propuesta de taller)

![Workbench con diagrama grande, badge Propuesta y chips de yield](img/03-workbench-corte.jpg)

Tras **Calcular**, el panel pasa al modo Workbench
(`.nwb-workbench`). La toolbar lleva ahora un **badge *Propuesta de
taller*** como recordatorio explícito: lo que ves es una propuesta
**revisable**, no un optimizador industrial — podés mover piezas con
drag, sacarlas a *sin colocar* o recalcular.

```
┌─ TOOLBAR: ← Setup │ Tablero i/n │ chips · flujo · $ │ Rotar │ ↻ │ PDF │ CSV │ Compra │ DXF │ Etiq ─┐
├──────────┬────────────────────────────────────┬──────────────────────────┤
│ Piezas   │     DIAGRAMA GRANDE                │ Cortes 1…N               │
│ + sin    │     drag · snap · rotate           │ click → highlight        │
│ colocar  │     cantos por lado · retazo       │ stats / offcuts          │
└──────────┴────────────────────────────────────┴──────────────────────────┘
```

El stage es un `<canvas>` que renderiza cada tablero en milímetros con
relación de aspecto real. Las piezas se colorean por espesor / material;
las rayas diagonales marcan veta. Las bandas perimetrales marcan los
cantos (rojo 0,45 mm, azul 2 mm, custom) y debajo de cada tablero va
una leyenda con esos mismos valores.

Al terminar el cálculo, sale un toast *Propuesta lista · mirá %
desperdicio y piezas sin colocar*.

> **Tres sub-paneles**: *Partes* (izquierda, items programados +
> sin colocar), *Diagrama* (centro, canvas interactivo), *Cortes*
> (derecha, 1…N pasos numerados con sus cotas).

## 4 · Anatomía del diagrama **cut-man**

![Detalle del diagrama con cut #s + leyenda de cantos](img/04-diagram-zoom-corte.jpg)

Cada rectángulo dentro del tablero codifica la misma info que antes,
pero el renderizador **cut-man** agrega tres elementos nuevos
(`src/nesting/render-cuts.ts`, `render-edge-legend.ts`,
`render-labels.ts`):

| Visual | Significado |
|--------|-------------|
| `rgba(214,138,61,0.28)` (ámbar) | Espesor 18 mm (melamina MDP por defecto). |
| `rgba(123,168,143,0.28)` (verde) | Espesor 15 mm (fondo / tapa de cajón). |
| `rgba(232,216,168,0.28)` (kerf) | Espesor 9 mm (MDF crudo / fondo económico). |
| rayas a 45° | `respectsGrain: 'h' \| 'v'` — la pieza no rota; bloquea reordenamientos. |
| **bandas rojas / azules** | Cantos en cada lado (rojo **0,45 mm** · azul **2 mm** · custom). |
| borde discontinuo verde | Retazo útil: puede volver al inventario como offcut. |
| **badges `#N`** | Secuencia de corte del paso N — mismo orden que la tabla del panel derecho. |
| **leyenda `0,45 mm / 2 mm`** | Esquina inferior derecha del tablero, swatches de las bandas. |

Click en un rectángulo lo selecciona en el rail izquierdo y dispara
`setNestHighlightedCutIndex` para iluminar el paso de secuencia
correspondiente en el panel derecho.

> El **PDF del nest** usa el mismo `renderBoard` con
> `showCutNumbers: true` y `showEdgeLegend: true`, así el diagrama
> que ves en pantalla es idéntico al que mandás al taller.
> Los labels tienen outline oscuro (`strokeText` con
> `rgba(14,39,64,0.85)`) para que se lean incluso sobre el ámbar
> saturado de MDP.

## 5 · Algoritmo: guillotine + maxrects + multi-restart

![Acordeón Avanzado del setup](img/05-advanced-corte.jpg)

El solver tiene tres modos (`src/nesting/types.ts:NestStrategyChoice`):

| Modo | Algoritmo | Cuándo |
|------|-----------|--------|
| **auto** (default) | corre **5 restarts** con estrategias distintas + shuffle seeded. Conserva el mejor. | Taller genérico / primera pasada. Recomendado. |
| **maxrects** | `packMaxRects` — heurística de máximo rectángulo libre (Best Short Side Fit). | Cuando querés minimizar retazos rectangulares grandes (CNC). |
| **guillotine** | `packGuillotine` — cortes válidos para sierra escuadradora (cada corte va de borde a borde). | Sierra de banco / escuadradora (el más rápido). |

En modo **auto**, `packNestMulti` (`src/nesting/multi-restart.ts`)
itera entre 5 sort strategies (`byMaxSide`, `byArea`,
`byPerimeter`, `byHeight`, `byAspectRatio`) con un shuffle seeded
(mulberry32). El criterio del mejor resultado depende ahora de
`Optimizar para`:

- **Material**: lexicográfico — menos tableros primero, después
  menor waste%.
- **Tiempo**: menor cycle time total (sumando setup + plunge +
  rapid + cut por tablero, calibrado por overhead).
- **Mixto**: blend de waste% y cycle time con `optimizeWeight`
  (0–1).
- **Por módulo**: menor *flow dispersion* — sumatoria de cuántos
  tableros distintos toca cada módulo (ver §7).

```ts
// Opciones que ve la UI (src/nesting/types.ts)
DEFAULT_NEST_OPTIONS = {
  kerf:         3,       // mm de disco
  borderMargin: 0,       // recorte de fábrica
  allowRotate:  true,
  strategy:     'auto',
  restarts:     5,
  timeBudgetMs: 5000,
  seed:         0,
  firstCut:     'v',
  optimize:     'material',
  optimizeWeight: 0.5,
  cutSequence:  'guillotine-naive',
}
```

## 6 · Cycle-time y secuencia de corte (TSP)

![Panel de cortes con cycle time](img/06-cycle-time-corte.jpg)

Una vez que el solver devuelve placements, un pipeline post-proceso
calcula:

1. **Cuts**: por cada tablero, el código genera una lista de
   `CutStep` (H o V + posición + longitud). Los cortes son los
   bordes internos del layout.
2. **Cut sequence**: el algoritmo elegido ordena los cortes. Tres
   opciones (`src/nesting/cut-sequence-tsp.ts:CutSequenceAlgorithm`):
   - `guillotine-naive` — sort por dirección (V primero) y luego
     por posición. Back-compat.
   - `nearest-neighbor` — greedy O(n²) partiendo de (0,0). Reduce
     aire vs guillotine-naive.
   - `tsp-2opt` — nearest-neighbor + swaps por pares que reduzcan
     path. **El que mejor resultado da**.
3. **Cycle time** (`src/nesting/cycle-time.ts`): por corte,
   `cut_time = cutLength / (feedRate × feedRateScale)`, más
   `rapid_time = airBeforeMm / rapidFeed`, más `plunge` en el primer
   corte, `toolchange` por cambio de herramienta (CNC), `flip`
   heurístico (CNC, cuando se cruza el eje Z), `setup` por tablero y
   `board_chg` entre tableros. Calibración final:
   `final_time = raw × (1 + overheadPct / 100)`, y
   `labor_cost = (final / 3600) × hourlyRate`.

El cycle time total ajustado por multiplicador de proyecto vive en
`result.totalTimeSec` (top-level del `NestResult`) y se muestra en el
cuts panel derecho.

> **Multiplicador de obra**: el cycle time del plan se multiplica por
> `settings.projectMultiplier` (clamp 1–99). Útil para cotizar un
> juego de cocina × 2 unidades — entrás dos veces la misma cantidad
> de piezas y el time final sale × 2 sin tocar el solver.

```ts
// Spec: docs/superpowers/specs/2026-07-18-cut-time-estimation.md
cut_time   = cutLength / (feedRate × feedRateScale)
rapid_time = airBeforeMm / rapidFeed
plunge     = first cut only      // panel saw
toolchange = per tool boundary   // CNC
flip       = heuristic (Z crossing) // CNC
setup      = setupTimeSec per board
board_chg  = boardChangeTimeSec per board // after first
final_time = raw × (1 + overheadPct / 100)
labor_cost = (final_time / 3600) × hourlyRate
```

## 7 · Optimizar por módulo (flujo A1)

![Cola de módulos en el setup con handles de reorder](img/08-module-queue-corte.jpg)

Cuando elegís `Optimizar para: Por módulo`, el solver agrupa las
piezas por **módulo de armado** (parent directo del leaf en el
treePath) y optimiza para que cada módulo quede lo más concentrado
posible — menos tableros distintos por módulo, mejor secuencia para
armar en taller. El setup muestra una **cola de módulos** debajo del
listado de stock:

- **Reordenar**: ↑ / ↓ en cada fila, o **drag & drop** para mover
  bloques enteros. El orden define el pack priority (`moduleRank`
  0 = primero).
- **Excluir**: × quita el módulo del nest; sus piezas caen a *Sin
  módulo* (rank alto).
- **Re-agregar**: + en la zona *Excluidos* lo vuelve a meter al
  final de la cola.
- ***Sin módulo*** (bucket sintético): piezas sin `treePath` o
  que no caen bajo ningún módulo. Rank alto por default; podés
  ordenarlo o quitarlo con × (sus piezas quedan sin colocar).

> Los módulos se resuelven desde el `treePath` de cada `BuiltPart`
> (`src/nesting/module-resolve.ts:modulesFromTreePaths`). Si el
> modelo es plano (sin jerarquía) o no tiene módulos reconocibles,
> la cola aparece vacía y el sistema cae al comportamiento de
> Material / Tiempo sin penalizarte.

### Flow chip en la toolbar

![Toolbar con chips de yield + chip de flujo](img/07-flow-chip-corte.jpg)

Tras correr con `optimize = by_module`, la toolbar muestra un chip
extra: **flujo X.X · N mod** (avg de tableros distintos por módulo).
Hover sobre el chip lista cada módulo con su span:

```
Bajo mesada → T1,T3 (span 2)
Alacena → T2 (span 1)
Mueble heladera → T4,T5 (span 2)
...
```

> Los `moduleStats` se preservan incluso cuando recalculás con
> `optimize = material` (commit `dc5c923`), así que el chip sigue
> mostrándose como referencia histórica.

## 8 · Edit SOTA: drag, snap, rotate

![Drag en el workbench con snap y rotate](img/07-edit-sota-corte.jpg)

El workbench expone un set de ediciones manuales SOTA (spec §Edit SOTA):

- **Drag** — mover una pieza. Snap a bordes de pieza, kerf y outline.
  `userMoved: true` tras commit.
- **Rotate `R`** — gira la pieza seleccionada 90°. Si la pieza
  respeta veta, la rotación queda bloqueada.
- **Cross-board** — arrastrar una pieza entre dos tableros del mismo
  espesor/material. Si no entra, el sistema lo rechaza con un
  transient.
- **Unscheduled** — quitar piezas del layout va al rail *Sin colocar*;
  desde ahí se pueden volver a poner en otro tablero.
- **Recompute** — cada commit dispara `recomputeBoardAfterEdit` que
  recalcula `usedArea`, `cuts` y stats del `result`.

El undo del workbench es **local** (no toca History 3D); se compone
con el history general sólo si lo engancha el panel.

## 9 · Tablero auto (phantom) + compra sugerida

Cuando el modelo tiene espesores / materiales que **no matchean con
el stock cargado**, el solver ya no tira error: genera **tableros
auto (phantom)** del tamaño mínimo necesario para las piezas huérfanas
(`src/nesting/material-pack.ts:generatePhantomStock`). Los phantoms
vienen con `phantom: true` y un badge `· phantom` en la cabecera del
tablero en el canvas. **Nunca** se persisten en
`despiece.stockboards` ni en `despiece-stock`.

### Banner colapsable

![Banner phantom colapsado por defecto](img/09-phantom-collapsed-corte.jpg)

Arriba del diagrama aparece un banner con la lista de phantoms
usados. **Por defecto está colapsado** (`<details>` cerrado, commit
`3a5273d`) para no robarle alto al canvas. Click en el resumen
abre el detalle:

![Banner phantom expandido con Guardar en mis tableros](img/10-phantom-expanded-corte.jpg)

- Cada fila muestra material · dimensiones · cantidad.
- **Guardar en mis tableros** → abre el material picker, promueve
  el phantom a `StockBoard` real (`promote-phantom.ts`) y lo suma al
  stock del nest.
- **También al inventario del taller** — si está tildado, replica
  en `despiece-stock` (IDB v2).
- *Hint*: "Cargá un tablero real o un preset para mejorar el
  aprovechamiento".

### Compra sugerida (CSV)

Si el solver usó phantoms, en la toolbar aparece el botón **Compra
sugerida** (`exportNestSuggestedBoards`). Click genera y descarga
`compra-sugerida-{modelo}.csv`:

```csv
material,espesor_mm,largo_mm,ancho_mm,cantidad,origen
Sin material,18,2750,1830,5,tablero_auto
```

El header es UTF-8 BOM + CRLF, decimales `,`, una fila por phantom
único (dedupeado por material × espesor × dims × cantidad).

## 10 · Exportar: PDF · CSV · DXF · Etiquetas · Piezas 2D

La toolbar del workbench tiene cinco botones de export, todos
sobre el resultado actual (respetan la selección del diagrama):

| Botón | Salida | Spec / módulo |
|-------|--------|----------------|
| **PDF** | Plan completo en PDF (diagrama grande por tablero + tabla de secuencia de cortes + cantos por pieza). | `src/export-nest-pdf/`, `src/ui/nesting-export.ts:exportNestPdf` |
| **CSV** | Listado pieza-a-pieza: coords, rotada, canto, material, tablero origen. Listo para abrir en Excel/Opticut. | `src/export-nest-csv/`, `src/ui/nesting-export.ts:exportNestCsv` |
| **Compra sugerida** | CSV de tableros auto generados (sólo cuando hubo phantoms). | `src/export/nest-suggested-boards.ts` |
| **DXF (.zip)** | **Un .dxf por tablero**, bundleados en `nest-{modelo}.zip`. Formato AutoCAD R14, capas `BOARD`, `PIECE`, `CUT`, `LABEL`, `GRAIN`. | `src/export-nest-dxf/`, spec `2026-07-18-export-nest-dxf.md` |
| **Etiquetas** | PDF de etiquetas pieza-a-pieza (datos para pegarlas en el taller). | `src/ui/nesting-export-labels.ts` |

### Etiquetas filtradas por tablero

Cuando el resultado tiene **≥2 tableros**, el botón *Etiquetas*
muestra el inline prompt `Etiquetas por tablero`:

![Prompt Etiquetas por tablero](img/12-labels-prompt-corte.jpg)

> Número de tablero a reimprimir (1, 2, 3, 4, 5). Vacío = todos.
> Actual: 1.

Default vacío = exporta todas las etiquetas. Ingresás un número y
sólo se exportan las piezas de ese tablero (filtrado por
`boardIndex` vía `export-labels/filter.ts:filterCutLabels`).

### Piezas 2D (.zip DXF) — desde el menú Exportar

Las **Piezas 2D** viven en `Exportar ▸ Piezas 2D` (no en la toolbar
del nest). Es un export **independiente del cálculo de nesting** —
útil para mandar a un outsource / CNC sin compartir el layout:

- Un DXF R14 **neto por pieza** (cantos descontados via
  `cutFaceDimensions`).
- Origen `(0, 0)` — listo para cargar en cualquier CAM.
- Label centrado: `nombre WxH` en capa `LABEL`.
- ZIP `piezas-2d-{modelo}.zip`.

> A diferencia del `nest-{modelo}.zip`, los DXF de piezas 2D **no
> llevan trayectorias ni G-code** — son geometría para cotización /
> nesting externo.

### Tabla resumen del output

| ¿Qué querés? | Botón | Spec |
|---------------|-------|------|
| Llevar el plan a la sierra | `PDF` | `2026-07-18-cut-time-estimation.md` (cycle time en cada tabla) |
| Abrir en OptiCut o Excel | `CSV` | — |
| Cargar en AutoCAD / CAM (un DXF por tablero) | `DXF (.zip)` | `2026-07-18-export-nest-dxf.md` |
| Pegar etiquetas en las piezas físicas | `Etiquetas` | `39ffc31` (filtro por tablero) |
| Cotizar / mandar a hacer afuera (un DXF por pieza) | `Exportar ▸ Piezas 2D` | `95f4ce5` |
| Comprar los tableros auto detectados | `Compra sugerida` | `b4d6188` |

## 11 · Ciclar semilla `R` y comparar

El atajo `R`, mientras el panel está abierto y **no está
computando**, dispara `triggerRegenerate` que es `runCompute(true)`
con un nuevo seed aleatorio. La historia de restarts vive en
`result.restartsHistory` y el mejor seed se persiste en
`result.bestSeed` (top-level del `NestResult`).

Comparando corrida tras corrida se ve el efecto del shuffle: una
pieza "rara" puede bajar 2–4 % el desperdicio cuando cae en un
casillero que el baseline dejaba vacío. En modo `auto` + 5 restarts,
el panel normalmente te muestra el mejor de los 5 al terminar.

> **Tiempo de cómputo**: subir `restarts` a 20 o `timeBudgetMs` a 30
> s puede duplicar el tiempo de cálculo sin mejorar el resultado.
> Para CocinaV9 (40–60 piezas) los defaults funcionan bien; subí
> restarts solo si tenés > 200 piezas o mezclas thicknesses.

## 12 · Atajos de teclado

`Help` (botón de la topbar o `?`) abre la hoja completa. Los más
usados en el flujo de nesting:

| Atajo | Acción |
|-------|--------|
| `N` | Abrir / cerrar panel de nesting (toggleNestingPanel). |
| `Esc` | Cerrar el panel (si no está computando). |
| `R` | Recalcular con semilla nueva — dentro del panel, solo si no hay computación en curso. |
| `R` (pieza seleccionada) | Rotar la pieza seleccionada 90° (siempre que `orientationsForGrain` lo permita). |
| Click + drag | Mover pieza en el workbench (snap-to-kerf). |
| Click rectángulo | Seleccionar pieza en el rail e iluminar corte en el cuts panel. |
| Doble click lista | Mover pieza seleccionada a *Sin colocar*. |
| `F` / `Home` | Fit / Fit + iso del viewport 3D. |

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

---

## Ver también

- [Tutorial · Cantos y auto-cantos](cantos-y-auto-cantos.html)
- [Tutorial · Roles y auto-roles](roles-y-auto-roles.html)
- Spec [`2026-07-10-nesting-optimizer.md`](../superpowers/specs/2026-07-10-nesting-optimizer.md)
  — diseño v1 del solver.
- Spec [`2026-07-11-nesting-v2.md`](../superpowers/specs/2026-07-11-nesting-v2.md)
  — v2, multi-material / grain.
- Spec [`2026-07-15-nesting-workbench-sota.md`](../superpowers/specs/2026-07-15-nesting-workbench-sota.md)
  — workbench SOTA (drag, snap, edit).
- Spec [`2026-07-18-cut-time-estimation.md`](../superpowers/specs/2026-07-18-cut-time-estimation.md)
  — cycle time + TSP.
- Spec [`2026-07-18-export-nest-dxf.md`](../superpowers/specs/2026-07-18-export-nest-dxf.md)
  — export DXF (.zip).
- Spec del diagrama cut-man + flow chip en `cut-man-a4` (PR mergeado 2026-08-05).