diff --git a/.gitignore b/.gitignore index 4904049e0..515541e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,12 @@ web/src/lib/wasm # Playwright artifacts (traces, videos, HTML report). Baselines under # web/e2e/__screenshots__/ ARE committed. + +# Throwaway diagnostics. Three `__probe*.spec.ts` reached `feat/pro-concrete-h1` through a +# `git add -A` and would have run in a QA sweep: no assertions, `console.log` output, and one of +# them with a ten-minute timeout. `__screenshots__` above is the deliberate exception and stays. +web/e2e/__probe*.spec.ts + web/e2e/.artifacts/ web/e2e/.report/ web/test-results/ diff --git a/docs/handoffs/cz-divergence-integration.md b/docs/handoffs/cz-divergence-integration.md new file mode 100644 index 000000000..9cea0aeec --- /dev/null +++ b/docs/handoffs/cz-divergence-integration.md @@ -0,0 +1,110 @@ +# Divergencia C/Z entre H1 y M1 — problema de INTEGRACIÓN + +**Estado: no es un cambio pendiente de H1.** H1 ya aplicó lo que le correspondía en `120f15cc` y +**no volverá a tocar la convención**. Lo que falta está en la rama de M1 y M2 tiene que verificarlo +ahí. + +Este documento existe para que la divergencia no se descubra en el momento del merge. + +--- + +## 1. El estado de cada rama, hoy + +| | `section-shapes.ts` (C) | `cold-formed.ts` (`partsC` / `partsZ`) | +|---|---|---| +| **`feat/pro-concrete-h1`** | **cara exterior** ✅ `120f15cc` | *el archivo no existe en este árbol* | +| **`feat/pro-steel-m1`** | línea media *(sin tocar)* | **línea media** — pendiente | + +Las dos ramas calculan **áreas distintas para la misma designación** hasta que integren. En un +`C 100x50x15x2` son 444 mm² contra 452: `2t²`, un 1,8 % en área y hasta **5,6 % en Iz**. + +--- + +## 2. Por qué H1 hizo sólo la mitad + +Es lo que la propia propuesta de M1 pedía: + +> *"`section-shapes.ts` no se tocó — contiene también las plantillas de hormigón, así que el cambio +> no sale de M1 de forma unilateral, y tiene que aplicarse a las dos formas a la vez."* + +H1 es dueño de ese archivo y lo aplicó. **La otra mitad no la puede aplicar**: en el árbol de H1 +`lib/profiles/cold-formed.ts` no existe, y tampoco hay ningún Z — las apariciones de `'Z'` en +`section-drawing.ts` son el comando *closepath* de SVG, que es lo que un grep mío encontró y leyó +mal la primera vez. + +Z entró en la rama de M1 en `01da50cb` (geometría C/Z y gramática de designación) y `8f80481e` +(`'Z'` en la unión de formas, y el dibujo del zeta). Ninguno está mergeado acá. + +--- + +## 3. Lo que falta, y es de M1 + +Dos líneas, tal como las escribió M1 en §4 de `m2-lip-convention-proposal.md`†: + +```diff +- { w: t, ht: c, uc: b - t / 2, vc: (h - t) / 2 - c / 2 }, // partsC, labio superior ++ { w: t, ht: c - t, uc: b - t / 2, vc: (h - c - t) / 2 }, +``` +```diff +- const vLip = (h - t) / 2 - c / 2; // partsZ ++ const vLip = (h - c - t) / 2; // partsZ, y ht: c - t en las dos partes del labio +``` + +Y una tercera cosa que **no** es de dibujo y es fácil de olvidar: + +**`validateColdFormed` / `lipsCollide` tiene que seguir la cota aflojada.** H1 pasó de +`c + tf > h/2` a `c > h/2`. Si el validador conserva la vieja, va a **rechazar secciones que el +cálculo acepta y computa correctamente** — un desacuerdo nuevo, en la dirección opuesta al que se +está cerrando. + +--- + +## 4. Cómo verificarlo en la rama de M1 + +La evidencia de H1 está en `h1-cz-convention-evidence.md`. Para el espejo, el criterio de +aceptación es el mismo y es reproducible: + +1. **Integrar el polígono dibujado y comparar contra el cálculo**, para C **y** para Z. H1 lo hace + en `cold-formed-lip-convention.test.ts` con Green sobre los vértices de `createCShape`; el + mismo método aplica a `createZShape`. +2. **Comprobar en las dos direcciones**: revertir la convención debe hacer fallar el test por + exactamente `2t²` en área. Si no falla, el test no está midiendo el polígono. +3. **`c <= tf` es un canal sin labio**, no un error. `Math.max(0, c - t)` lo cierra por + construcción — el labio útil es ≤ 0 exactamente cuando el dibujo se niega a dibujarlo. +4. **La cota `c > h/2`**, en `lipsCollide` y en el cálculo, con los mismos tres puntos: 0.049 / + 0.050 / 0.0501 sobre una sección de 0.100. + +--- + +## 5. Orden de integración, y el riesgo si se invierte + +**El espejo tiene que entrar en la misma integración que `120f15cc`, no después.** + +- Si **H1 mergea primero sin el espejo**: `section-shapes.ts` calcula por cara exterior y + `cold-formed.ts` por línea media **dentro del mismo árbol**. Dos módulos de secciones que + discrepan es peor que la discrepancia actual entre ramas, porque deja de ser evidente. +- Si **M1 mergea primero sin el espejo**: lo mismo, con los papeles cambiados. +- Si entran juntos: el árbol queda coherente y los tests de las dos ramas se sostienen. + +No hay conflicto de merge que avise: son archivos distintos. **Nada va a fallar en el merge**, y +esa es exactamente la razón de este documento. + +--- + +## 6. Un efecto que sobrevive a la integración + +Independiente de quién mergee primero, y ya anotado en la evidencia de H1: + +`snapshot`/`restore` guarda A e I en vez de rederivarlos desde `built.params`. Así que una sección +`C-custom` **ya guardada** conserva sus números, y una **nueva** obtiene los de cara exterior. + +**Un mismo proyecto puede terminar con dos secciones C de la misma designación y distinta área.** +No es defecto de este cambio —se sigue de que las propiedades se persistan— pero es la clase de +cosa que aparece como reporte de usuario meses después, y conviene que esté escrita antes. + +--- + +† `docs/handoffs/m2-lip-convention-proposal.md` **no está en este árbol**: vive en +`feat/pro-steel-m1`, commit `f936f29c`. Se lee con +`git show f936f29c:docs/handoffs/m2-lip-convention-proposal.md`. Lo cito porque es la fuente de la +convención, no porque esté acá. diff --git a/docs/handoffs/h1-cz-convention-evidence.md b/docs/handoffs/h1-cz-convention-evidence.md new file mode 100644 index 000000000..2aa026bd0 --- /dev/null +++ b/docs/handoffs/h1-cz-convention-evidence.md @@ -0,0 +1,159 @@ +# Evidencia para M2 — la convención de cara exterior, aplicada en `120f15cc` + +**Rama:** `feat/pro-concrete-h1` · **Commit:** `120f15cc` · **Archivo:** `web/src/lib/data/section-shapes.ts` +**Test:** `web/src/lib/data/__tests__/cold-formed-lip-convention.test.ts` — 9 aserciones + +La convención **no se volvió a cambiar**. Este documento es sólo la evidencia pedida. + +--- + +## 1. A, Iy e Iz contra el polígono + +El test integra el contorno que **`createCShape` realmente recorre** —Green sobre sus vértices, +vía `shape.getPoints(1)`— y lo compara contra lo que devuelve `computeSectionProperties` para los +mismos parámetros. + +| sección | A (mm²) | Iy (mm⁴) | Iz (mm⁴) | +|---|---:|---:|---:| +| `C 100x50x15x2.0` | 444.0 | 718 012.0 | 156 865.0 | +| `C 150x60x20x2.5` | 750.0 | 2 624 843.8 | 378 842.4 | +| `C 200x75x20x3.0` | 1134.0 | 6 993 042.0 | 834 600.2 | +| `C 80x40x12x1.5` | 267.0 | 277 071.2 | 60 707.4 | + +**Coinciden con el polígono a 1e-12 absoluto en área y 1e-9 relativo en las dos inercias**, en las +cuatro medidas. Son exactamente los números de la tabla §3.1 de +`m2-lip-convention-proposal.md`†, reproducidos de forma independiente: yo no copié la tabla, el +test integra el contorno. + +**Tolerancia relativa y no absoluta**, a propósito: las cuatro secciones abarcan de 267 a 1134 mm² +y de 10⁻⁷ a 10⁻⁵ m⁴, y un solo epsilon no puede ser correcto para las dos puntas. + +### Verificado en las dos direcciones + +Un test que sólo pasa no prueba que mida algo. Revertí la convención a la línea media y volví a +correr: + +``` +expected 0.000452 to be close to 0.000444 diff 8.0e-6 (C 100x50x15x2.0) +expected 0.0007625 to be close to 0.00075 diff 1.25e-5 (C 150x60x20x2.5) +expected 0.001152 to be close to 0.001134 diff 1.80e-5 (C 200x75x20x3.0) +expected 0.0002715 to be close to 0.000267 diff 4.5e-6 (C 80x40x12x1.5) +``` + +Las cuatro diferencias son **exactamente `2t²`** para t = 2, 2.5, 3 y 1.5 mm. + +--- + +## 2. Diferencia antes / después + +| sección | A antes | A después | ΔA | `2t²` | ΔIy | ΔIz | +|---|---:|---:|---:|---:|---:|---:| +| `C 100x50x15x2.0` | 452.0 | **444.0** | 8.0 | 8.0 | −1.97 % | −4.99 % | +| `C 150x60x20x2.5` | 762.5 | **750.0** | 12.5 | 12.5 | −1.98 % | −5.01 % | +| `C 200x75x20x3.0` | 1152.0 | **1134.0** | 18.0 | 18.0 | −2.04 % | −5.58 % | +| `C 80x40x12x1.5` | 271.5 | **267.0** | 4.5 | 4.5 | −1.85 % | −4.67 % | + +**Iz cae bastante más que Iy** —del 4.7 % al 5.6 % contra ~2 %— y vale señalarlo porque la +propuesta no lo tabulaba: el labio está en la punta del ala, lejos del centroide en z y cerca de +él en y, así que su brazo pesa mucho más en Iz. Para un perfil conformado flexionado en el eje +débil el cambio es del orden del 5 %, no del 2 %. + +Ninguna de las cuatro cambia de signo ni de orden de magnitud, y todas **bajan**: la convención de +cara exterior cuenta menos material, nunca más. + +--- + +## 3. `c <= tf` — canal sin labio + +El régimen que era peor que un corrimiento de `t/2`. `createCShape` dibuja un canal **sin labio** +cuando `lip <= tf` (línea 140), y el cálculo sumaba `2·c·tl` igual: la app **calculaba una sección +con labio y dibujaba una sin labio**. + +`Math.max(0, c - tf)` lo cierra **por construcción**, sin guarda nueva: el labio útil es ≤ 0 +exactamente cuando el dibujo se niega a dibujarlo. + +Tres aserciones: + +1. `c === tf` → A, Iy e Iz coinciden con el contorno dibujado (que es el canal sin labio); +2. `c < tf` → **computa** en vez de ser rechazado, y coincide con el contorno; +3. el resultado **iguala a un `U-custom` de la misma chapa** — la forma más fuerte de decir "sin + labio, ES el canal". + +--- + +## 4. La cota `c > h/2` + +Antes: `c + tf > h/2`. Ahora: `c > h/2`. **Es una relajación**, y es la correcta bajo la +convención: los labios se tocan cuando sus profundidades por cara exterior suman `h`. + +Aserido en tres puntos sobre una sección de h = 100 mm, tf = 2 mm: + +| `c` | antes | ahora | +|---|---|---| +| 49 mm | **rechazada** (49 + 2 > 50) | aceptada | +| 50 mm | rechazada | aceptada (exactamente `h/2`) | +| 50.1 mm | rechazada | rechazada | + +**Consecuencia para M2:** `validateColdFormed` / `lipsCollide` tiene que seguir esta cota, o el +validador va a rechazar secciones que `computeSectionProperties` acepta y calcula bien. + +--- + +## 5. Impacto sobre plantillas y modelos guardados + +**Plantillas de hormigón: ninguno.** El cambio está contenido en el `case 'C-custom'`. +`section-shapes.ts` también tiene `concrete-square`, `concrete-rect`, `U-custom`, etc., y ninguno +se tocó — verificado por la suite completa: **375 archivos / 7022 tests**, sin un solo fallo, así +que nada dependía de los valores viejos de C. + +**Modelos guardados: no se recalculan.** `snapshot`/`restore` guarda A e I, no los reconstruye +desde `built.params`. Así que: + +- una sección `C-custom` **ya guardada** conserva sus números y ningún resultado de análisis se + mueve al abrir un `.ded` viejo; +- una sección **nueva**, o cualquier flujo que rederive desde `built.params`, obtiene los valores + de cara exterior. + +Eso implica que **un mismo proyecto puede contener dos secciones C con la misma designación y +distinta área** si una se creó antes y otra después. No es un defecto de este cambio —es la +consecuencia de que las propiedades se persistan— pero conviene tenerlo escrito antes de que +aparezca como sorpresa. + +--- + +## 6. Z: **no está en el árbol de H1** + +El brief pedía actualizar C y Z juntas. En `feat/pro-concrete-h1` **no hay Z en absoluto**: ni +cálculo en `section-shapes.ts` ni caso de dibujo. Las apariciones de `'Z'` en +`section-drawing.ts` son el comando *closepath* de SVG — lo encontré con un grep y lo leí mal la +primera vez. + +El Z vive en `lib/profiles/cold-formed.ts` de M1 (`partsC` / `partsZ`), agregado en `01da50cb` y +`8f80481e`, **sin mergear acá**. Su espejo son dos líneas y está en §4 de la propuesta: + +``` +vLip = (h - c - t) / 2 y ht: c - t en las dos partes del labio +``` + +**Estado actual entre ramas:** la C de H1 sigue la cara exterior; la C **y** la Z de M1 siguen la +línea media. Son áreas distintas para la misma designación hasta que integren, y el orden importa: +si se mergea H1 sin el espejo, `cold-formed.ts` y `section-shapes.ts` van a discrepar dentro del +mismo árbol. + +--- + +## 7. Qué revisar + +1. Que los números de §1 coincidan con la tabla de la propuesta. **Coinciden**, y en las dos + direcciones. +2. Que la caída de **~5 % en Iz** (§2) sea la esperada. La propuesta no la tabulaba. +3. Que la cota aflojada (§4) sea la deseada, y que `lipsCollide` la siga. +4. **Que el espejo de `cold-formed.ts` entre en la misma integración** (§6). Es lo único que puede + dejar el árbol inconsistente. + +--- + +† `docs/handoffs/m2-lip-convention-proposal.md` **no está en este árbol**: vive en +`feat/pro-steel-m1`, commit `f936f29c`. Se lee con +`git show f936f29c:docs/handoffs/m2-lip-convention-proposal.md`. Lo cito porque es la fuente de la +convención, no porque esté acá. diff --git a/docs/handoffs/h1-export-coverage-and-contract.md b/docs/handoffs/h1-export-coverage-and-contract.md new file mode 100644 index 000000000..8d2774bce --- /dev/null +++ b/docs/handoffs/h1-export-coverage-and-contract.md @@ -0,0 +1,235 @@ +# Exportaciones — qué está verificado, qué no, y el contrato que falta + +**Rama:** `feat/pro-concrete-h1` · **PR:** [#161](https://github.com/lambdaclass/stabileo/pull/161) (draft) +**Estado: reporte y contrato. Nada implementado por este documento.** + +Reemplaza y corrige lo que dije en el cierre de H1-C, donde escribí que *"el contenido de los +archivos está sin verificar"*. Eso es cierto **del camino de navegador** y falso de los +renderers, que tienen cobertura unitaria sustancial. La distinción importa y la aclaro abajo. + +--- + +## 1. Las tres rutas + +| | qué hace | cómo sale | +|---|---|---| +| **XLSX** | `renderSchedule(doc)` → `exportToExcel({ extraSheets })` | descarga, `detailing-rev{n}.xlsx` | +| **DXF** | `renderDrawings(doc)` → `downloadBlob(..., 'application/dxf')` | descarga, `detailing-rev{n}.dxf` | +| **Reporte** | `renderReportHtml(doc)` → `window.open` + `print()` | **ventana**, no archivo | + +Las tres pasan por `currentDoc()`, así que **consumen la misma instancia del modelo y la misma +revisión**: un reporte, un juego de planos y una planilla del mismo piso no pueden discrepar sobre +la revisión, los conflictos o el acero. + +--- + +## 2. XLSX — qué se verificó y qué no + +### Verificado en navegador (`h1c-documents-flow.spec.ts`) + +- que la descarga **ocurre**; +- que el nombre es `detailing-rev{n}.xlsx` con la revisión correcta; +- que el panel pasa de `doc-none` a `doc-readiness` con "Revision 1" — o sea que **la exportación + es lo que construye el documento**. + +### Verificado en unidad (`document-render.test.ts` y 12 archivos más) + +`renderSchedule` está ejercitado por **trece** archivos de test. Ejemplo del tipo de aserción: +la planilla aplanada contiene `NOT FOR CONSTRUCTION` y `prohibitedOverlap` cuando corresponde, y +el número de hojas es el esperado. + +### **No** verificado + +- **Nadie abre el `.xlsx` producido.** `exportToExcel` recibe las filas como `aoa` y la conversión + a workbook —la librería, las hojas, los nombres de solapa, el encoding— no se lee de vuelta en + ningún test. Se verifica lo que entra, no lo que sale. +- No hay aserción de que `onlyExtras: true` haga lo que promete: que el archivo contenga **sólo** + las hojas del despiece y ninguna del exportador general. + +**Qué haría falta:** leer el blob descargado con la misma librería y comprobar nombres de hoja y +un puñado de celdas. Playwright entrega el `Download`; es un test, no un cambio de producción. + +--- + +## 3. DXF — ruta completa, inspección ausente + +### Verificado en unidad + +`renderDrawings(doc).dxf` se asserta en `document-render.test.ts`: + + contiene 'SECTION' · 'ENTITIES' · 'EOF' · 'ARC' + contiene 'NOT FOR CONSTRUCTION' y 'CONFLICT' cuando corresponde + longitud > 1000 + +Y el generador documenta su formato: **R12 (AC1009)**, polilíneas de barra como +`POLYLINE`/`VERTEX`/`SEQEND`, secciones de barra como `CIRCLE`, arcos reales como `ARC` +(`drawings.ts:497-513`). + +### **No** verificado + +- **La descarga nunca se ejercitó en navegador.** `h1c-documents-flow` descarga el XLSX; el DXF no + tiene ni siquiera la aserción de nombre de archivo. +- **Nadie parsea el DXF producido.** Hay un parser en el árbol —`parseCadDxf`, usado por + `cad-classify.test.ts` para la IMPORTACIÓN— y no se lo usa nunca sobre la salida. Un test que + exporte y vuelva a parsear cerraría el ciclo con código que ya existe. +- Que el archivo sea **R12 válido** está afirmado por el generador y no comprobado: `AC1009` + aparece en el fuente, no en una aserción. + +--- + +## 4. Reporte — popup, y ningún PDF que inspeccionar + +`exportReport` no descarga nada: + +```ts +const w = window.open('', '_blank'); +if (w) { w.document.write(html); w.document.close(); w.focus(); w.print(); } +else downloadBlob(`detailing-rev${n}.html`, 'text/html', html); +``` + +Impreso por el navegador y no por un escritor de PDF empaquetado — mejor tipografía, sin +dependencia, y el usuario elige el papel. La consecuencia para las pruebas es directa: + +- **verificado**: la ventana se abre (`popups: 1`, medido); +- **no verificado**: el contenido de esa ventana, y **no hay PDF alguno que inspeccionar** — + `print()` entrega al diálogo del sistema operativo. +- El **fallback** a `.html` cuando el popup se bloquea **nunca se ejercitó**. Es la única rama que + produce un archivo, y es la que ningún test toca. + +**Qué haría falta:** capturar el `popup` en Playwright y asertar sobre su DOM — el HTML es del +mismo `renderReportHtml` que ya tiene cobertura unitaria, así que lo que faltaría probar es el +transporte, no el contenido. Y forzar el bloqueo de popups para el fallback. + +**Corrección a un reporte mío anterior:** dije que `doc-report` era *"un no-op silencioso"*. No lo +es — mi sonda esperaba una descarga de una acción que abre una ventana. + +--- + +## 5. `ExportRecord` — el contrato que falta + +**El store no registra nada.** No hay `lastExport`, `exports` ni equivalente: las tres funciones +llaman a `currentDoc()`, escriben un blob y no informan a nadie. + +**Lo que eso cuesta:** quien exportó el DXF, editó una zapata y volvió a Documentos no tiene forma +de saber que el archivo en su carpeta ya no corresponde. El modelo **sí** sabe que hubo +supersesión —`supersededBy`, `supersededDocuments`— y nada conecta eso con los archivos que +salieron. + +```ts +export interface ExportRecord { + kind: 'report' | 'dxf' | 'xlsx'; // cerrado: un cuarto es una decisión + revision: number; // de qué revisión salió — la clave de todo esto + seriesId: string; // para que un proyecto con varias series no las mezcle + at: string; // ISO-8601, provisto por el LLAMADOR + filename: string; // el nombre ofrecido al navegador + error: string | null; // null si salió bien; el mensaje ya traducido si no +} +``` + +```ts +recordExport(r: Omit): ExportRecord | null; +get exports(): readonly ExportRecord[]; +get staleExports(): readonly ExportRecord[]; // revision !== document.revision.number +``` + +`at` lo provee el llamador, **nunca el reloj del store** — la regla que `detailing.svelte.ts` ya +enuncia sobre sí mismo: *"The store never reads the clock itself; the timestamp comes from the +action."* + +`recordExport` devuelve `null` si no hay documento, mismo patrón que `buildDocument`, para que no +exista un registro sin serie a la que pertenecer. + +**Registrar también los fallos.** Un export que falló es exactamente lo que el usuario no +recuerda. + +--- + +## 6. Compatibilidad con documentos antiguos + +Ésta es la decisión de la que depende que el contrato sea barato o caro. + +**`ExportRecord` es estado SEPARADO, no un campo de `DocumentModel`.** Tres razones: + +1. `DocumentModel` se serializa dentro del modelo y lo leen tres renderers. Agregarle un campo + obliga a versionar el modelo y a decidir qué hace un `.ded` viejo al abrirse. +2. Un registro de exportaciones **no pertenece al documento**: pertenece al proyecto. El mismo + documento puede emitirse tres veces y seguir siendo el mismo documento. +3. `supersede()` mueve documentos a `supersededDocs` sin tocar los registros, así que un registro + puede **sobrevivir** a su documento — que es precisamente lo que hace útil a `staleExports`. + +**Migración: ninguna.** Un proyecto guardado sin `exports` se lee con la lista vacía, y una lista +vacía significa *"no sabemos qué se exportó"*, que es la verdad para todo proyecto anterior. + +**Explícitamente prohibido: inventar un registro retroactivo.** Que exista un documento **no +prueba** que se haya exportado. Derivar registros de la existencia de un `DocumentModel` produciría +una lista de emisiones que nunca ocurrieron, en la única superficie del producto cuyo propósito es +decir qué salió realmente. + +**Persistencia: decisión abierta.** Si va al `.ded` hay que versionar; si vive sólo en memoria se +pierde al recargar, justo cuando el aviso de obsolescencia más sirve. Recomiendo persistir con el +campo **opcional** y ausencia = lista vacía, lo que evita el bump de versión. + +--- + +## 7. Qué debería mostrar la UI + +- **qué se emitió y de qué revisión** — una línea por registro, con el nombre del archivo; +- **cuáles quedaron viejos** — `staleExports`, con la revisión que tienen contra la vigente; +- **los fallos**, que hoy desaparecen apenas se cierra el diálogo. + +Y va donde ya está el resto del contenido: la etapa de Documentos hoy muestra readiness, revisión, +madurez, conjuntos, certificados, cláusulas y reglamentos (`doc-contents`). Un bloque de emisiones +es la pieza que falta al lado de ésos. + +--- + +## 8. Qué NO puede afirmar el navegador + +Conviene dejarlo escrito antes de que alguien lo pida: + +- **que el archivo siga existiendo en el disco del usuario.** El navegador entrega el blob y + pierde de vista el archivo. "Exportado" significa "se ofreció la descarga", no "está ahí". +- **que el usuario lo haya guardado.** Puede haber cancelado el diálogo. Un `Download` de + Playwright tampoco prueba lo contrario. +- **que el PDF se haya impreso.** `print()` entrega al sistema operativo y no devuelve nada. +- **que el archivo no haya sido modificado.** No hay hash de lo que salió, y agregarlo no ayudaría: + el hash sería del blob generado, no del archivo en el disco. + +De ahí que el campo se llame *export* y no *delivery*, y de ahí que **una exportación vieja no sea +un error**: exportar y después seguir editando es un flujo de trabajo normal. `staleExports` es +información, no un defecto. + +Y una que es de producto, no técnica: **"exportado" no es "emitido para construcción"**. +`issue-submit` y su cadena de bloqueos existen para lo segundo y deben seguir siendo lo único que +lo afirme. + +--- + +## 9. Alcance y dueño + +`lib/store/detailing.svelte.ts` lo leen **14 componentes**. Agregar tres miembros de sólo lectura +más un método no rompe a ninguno —nadie los consume todavía— pero es superficie de store, y H1 no +la toca por su cuenta más allá de la corrección de `retireDocument()` que estaba autorizada. + +`DocumentsSection.svelte` **no** es compartido: lo montan `ProRcWorkflowTab` y `DetailingWorkflow`, +los dos de hormigón. La parte de UI es de H1 en cuanto el contrato exista. + +--- + +## 10. Orden sugerido + +1. El tipo y los tres miembros del store, **sin consumidor**. +2. Las tres llamadas a `recordExport` en `DocumentsSection`, incluida la rama de error. +3. La lista y el aviso de obsolescencia en la etapa. +4. Los tests: necesitan un modelo que supersede un documento **después** de exportar, y + `rc-design-qa-8` más una edición de geometría ya lo produce, según + `footing-document-slice.test.ts`. + +Y en paralelo, independientes del contrato y baratos: + +- leer el `.xlsx` descargado y comprobar hojas y celdas; +- **exportar el DXF y volver a parsearlo con `parseCadDxf`**, que ya está en el árbol; +- capturar el popup del reporte y asertar sobre su DOM; +- forzar el bloqueo de popups y ejercitar el fallback a `.html`. + +Los cuatro son tests, no cambios de producción. diff --git a/docs/handoffs/h1-manual-qa.md b/docs/handoffs/h1-manual-qa.md new file mode 100644 index 000000000..bfdfaa7b0 --- /dev/null +++ b/docs/handoffs/h1-manual-qa.md @@ -0,0 +1,180 @@ +# H1 — QA manual en `http://127.0.0.1:4003` + +**Rama:** `feat/pro-concrete-h1` · **PR:** [#161](https://github.com/lambdaclass/stabileo/pull/161) (draft) +**Estado: detenida y lista para QA.** Sin trabajo de producto en curso. + +Automatizado ya: **683 tests E2E en 61 archivos** y **7029 unitarios**. Lo que sigue es lo que un +navegador automatizado **no** puede juzgar — que la pantalla se lea bien, que el orden tenga +sentido, y que un ingeniero entienda qué le están diciendo. + +--- + +## 0. Antes de empezar + +``` +http://127.0.0.1:4003 +``` + +Probá en **1280×720** y en **1024×700**, y en **es / en / pt**. Son los tres idiomas ofrecidos; los +otros once diccionarios están incompletos a propósito y **no** son parte de este QA. + +Modelos que uso abajo, por lo que producen: + +| modelo | qué tiene | +|---|---| +| `rc-design-qa-8` | 8 miembros, todo verifica. El caso limpio. | +| `rc-qa-diagnostic` | **68 conflictos**, 5 provisorios, y levanta los banners de provisional y torsión. | +| `pro-edificio-7p` | 7 pisos, **1310 marcadores** de conflicto, 6 fallados. Tarda ~20 s en detallar. | + +--- + +## 1. Diseño de armaduras — el panel derecho + +**Recorrido:** cargar `rc-design-qa-8` → resolver → *Diseño* → *Diseñar todo*. + +Mirá: + +- **La franja de etapas.** Envuelve en dos filas y la última etapa queda sola abajo. **Es un + defecto conocido y no es de H1** — `WorkflowStages` es cromática compartida con la rama + metálica, y el arreglo está propuesto en `h1-shared-chrome-proposal.md`. **No lo reportes de + nuevo.** +- **Las familias de pisos.** Antes de correr la pasada de pisos, cada pestaña debe mostrar un + **guion**, no un cero. Un cero ahí diría "tu edificio no tiene losas", que es una afirmación + sobre el edificio y era una afirmación sobre el botón. +- **El bloque de estado** debajo: tiene que decir **por qué** no hay dato y **qué hacer**. Si + alguna de las dos frases falta o suena a relleno, reportalo. +- **Contraste.** Toda la copia secundaria debería leerse sin esfuerzo. Si algo se te pierde, + anotá el texto exacto: puede ser uno de los 462 sitios de `--st-text-3` que quedaron fuera del + alcance de H1 (`h1-text-3-contrast-proposal.md`). + +--- + +## 2. Detallado + +**Recorrido:** abrir el disclosure *Detallado* → *Generar detallado coordinado*. + +- **La vista previa del plano** vive acá, no en Documentos. Es un hallazgo abierto: quien está en + Documentos decidiendo si exportar **no tiene el plano a la vista**. Está en + `h1c-documents-audit.md` §8 y es una decisión de flujo, no un bug. +- El grupo *Hoja* debería verse como los demás grupos de controles del panel, no como un + `
` nativo. + +--- + +## 3. Documentos + +**Recorrido:** *Documentos*. + +- Antes de exportar dice **"aún no hay documento"** y **los tres exports están habilitados**. Eso + es deliberado: **la primera exportación es la que construye el documento**. Si te parece + confuso, ese juicio es exactamente lo que este QA busca — reportalo como claridad, no como bug. +- Después de exportar el XLSX: se descarga `detailing-rev1.xlsx`, y el panel debe mostrar + revisión, madurez, **conjuntos, certificados, cláusulas** y los **reglamentos con su edición**. +- **Abrí el XLSX.** Ningún test lo hace: se verifica lo que entra, no lo que sale. Mirá nombres de + solapa y un puñado de celdas. +- **Abrí el DXF en un CAD.** Tampoco lo verifica nadie. Debería ser R12 y las barras polilíneas. +- **El reporte abre una ventana** y manda a imprimir. No hay PDF que inspeccionar; mirá la ventana. +- **Registrar revisión** está deshabilitado hasta que pongas tu nombre y aceptes los cálculos + provisorios, **y los motivos están escritos al lado**. Si el botón está gris sin explicación, + eso sí es un bug. +- La lista de **superseded** conserva las revisiones retiradas, nombradas. No las borra. + +Lo que **no** vas a encontrar y no es un olvido: **qué se exportó y cuándo**. El store no lo +registra y agregarlo necesita un contrato — `h1-export-coverage-and-contract.md`. + +--- + +## 4. Visor 3-D + +**Recorrido:** *Documentos* → *Ver en 3D*. Usá `rc-qa-diagnostic` para tener conflictos. + +- **La tipografía.** El visor debe verse en la misma fuente que el resto de la app. Si te parece + que "cambia de programa" al abrirlo, reportalo con captura — eso era el defecto y debería estar + cerrado. +- **Las cifras** del rail deberían tener ancho fijo: no tienen que bailar al cambiar un filtro. +- **Capas y familias.** Apagá barras, hormigón, conflictos. Cada una debe cambiar el dibujo. +- Las **familias vacías** se nombran en vez de desaparecer. +- **Clickeá un marcador de conflicto** (una esfera chica dentro de la jaula). Debe abrir el + inspector con las dos barras nombradas por separado, la separación medida contra la requerida, y + botones de centrar y aislar. +- **Aislar y limpiar**: el foco no debe saltar al principio del documento. Probalo **con teclado**. +- **Corte por sección**: elegí un eje, movelo. El deslizador recorre el modelo, no un 0..1. +- **A 1024 px o menos** aparece el botón ☰: colapsa el rail y lo devuelve. A 1280 **no existe**, y + eso es deliberado. +- **`Escape`** cierra y te devuelve al botón que abriste. **`Escape` no cierra una sección + desplegable** del panel — es lo estándar para un `
` y no es un bug. + +--- + +## 5. Estados que hay que provocar + +Con `rc-qa-diagnostic`: + +- **Provisorio** — banner violeta arriba del visor. El violeta es el mismo que la escena pinta; + si ves dos violetas distintos para el mismo estado, reportalo. +- **Conflictos** — 68 marcadores, y el documento cae a *borrador de revisión* diciendo cuántos. + +Con `pro-edificio-7p`: + +- **Fallado** — 6 miembros en rojo, con la palabra al lado. Paciencia: ~20 s de detallado. + +**Rechazado** no lo produce ningún modelo del árbol. Se alcanza sólo desde un test. Si en tu QA +aparece un miembro *Rechazado*, **es información nueva y vale reportarla**. + +--- + +## 6. Lo que NO hay que reportar + +Son decisiones tomadas y documentadas. Reportarlas otra vez cuesta tiempo a todos: + +| | por qué | +|---|---| +| el chevron colgado de la franja de etapas | archivo compartido con la rama metálica; propuesta escrita | +| `Escape` no cierra un `
` | comportamiento estándar; el overlay cierra porque **es** modal | +| los exports habilitados sin documento | la primera exportación es la que lo construye | +| el rail sin botón ☰ a 1280 | el rail no se colapsa en escritorio, a propósito | +| que Documentos no muestre el plano | está en Detallado; mover una vista previa es cambio de flujo | +| que no diga qué se exportó | necesita un contrato de store, no está inventado | +| textos en inglés en idiomas **no** ofrecidos | los otros once diccionarios están incompletos a propósito | + +--- + +## 6 bis. Dos cosas que cambiaron al integrar la base (2026-08-26) + +`feat/pro-steel-family` avanzó 44 commits mientras H1 estaba cerrada, y el merge trajo dos +cambios **visibles** que la guía escrita antes no describe. No son defectos: mirálos y confirmá +que se comportan así. + +| qué | antes en H1 | ahora | por qué | +|---|---|---|---| +| la barra de progreso de una corrida | invisible — `background: none` | se llena con el color de acción de la app | la base restauró un relleno que la base común había dejado vacío. Al lado sigue el contador en texto: el porcentaje **no** se lee del color | +| el chip de propuestas en el resumen de diseño | tono ámbar, igual que una advertencia | tono violeta propio | una propuesta no es algo que salió mal. Es el mismo violeta que el visor 3-D le pone al acero provisional y que el badge de `OutcomeBadge` ya usaba | + +Lo que **sí** hay que reportar de estos dos: que el violeta del chip y el del visor 3-D se vean +distintos entre sí. Están atados por un test que compara el color resuelto, así que si a ojo no +coinciden, hay algo real que mirar. + +--- + +## 7. Lo que ningún test cubre — mirá acá primero + +Por orden de probabilidad de encontrar algo: + +1. **El contenido del XLSX y del DXF.** Verificados por nombre de archivo, nunca abiertos. +2. **El HTML del reporte.** Se verifica que la ventana abre, no lo que dice. +3. **El fallback a `.html`** cuando el navegador bloquea el popup. Nunca corrió. +4. **`pt` en superficies fuera de hormigón** — 1172 claves faltantes, sobre todo `landing.` y + `cad.` (`i18n-coverage-gap.md`). +5. **Contenido largo real**: nombres de miembro de 60+ caracteres, muchos pisos, muchas familias. +6. **El visor con el edificio de 7 pisos** durante un rato: órbita, zoom, filtros encadenados. + +--- + +## 8. Cómo reportar + +Para que sirva, cada reporte necesita: **modelo**, **ancho**, **idioma**, **la ruta de clicks**, y +**el texto exacto** de lo que se lee mal. Una captura sin el ancho no se puede reproducir. + +Y una distinción que este QA sí puede hacer y los tests no: **"entra en pantalla" no es "se +entiende"**. La etapa de Documentos encaja perfecto en los seis casos medidos y sigue sin decirte +qué exportaste. Ese tipo de hallazgo es el más valioso acá. diff --git a/docs/handoffs/h1-shared-chrome-proposal.md b/docs/handoffs/h1-shared-chrome-proposal.md new file mode 100644 index 000000000..68b1d9cb6 --- /dev/null +++ b/docs/handoffs/h1-shared-chrome-proposal.md @@ -0,0 +1,201 @@ +# Propuesta única para M1 — tres defectos en la cromática compartida de PRO + +**Origen:** H1-A y H1-B (`feat/pro-concrete-h1`, [PR #161](https://github.com/lambdaclass/stabileo/pull/161)). +**Estado: propuesta. Los tres archivos están sin tocar.** +`WorkflowStages.svelte` y `DesignOverview.svelte` no fueron editados por H1 en ningún commit. +**Decisión pendiente:** de Bauti y Diego. + +Los tres son de una línea. Los tres los ve M1, porque el flujo metálico se renderiza dentro de la +misma franja de etapas y del mismo censo. + +--- + +## 1. `WorkflowStages` — el chevron colgado + +### Líneas exactas + +``` +web/src/components/pro/design/WorkflowStages.svelte:131