- Refactored QuoteItemsSortableDataTable to reduce lines from 593 to 88, maintaining API and re-exporting types. - Introduced QuoteItemsAddActions for centralized row/article/block addition buttons. - Created QuoteItemsEmptyState to handle empty states with title, description, icon, and actions. - Separated components for table header, body, and drag overlay for better modularity. - Implemented useQuoteItemsSortableTable hook to manage row selection, drag-and-drop functionality, and item manipulation. - Added tests for the new hook to ensure functionality for duplication, deletion, and drag-and-drop operations. - Updated documentation to reflect changes and improvements in the quote items table structure.
469 lines
76 KiB
Markdown
469 lines
76 KiB
Markdown
# Capítulos en cotizaciones: auditoría y plan
|
|
|
|
Fecha: 19 de octubre de 2026. Estado: fases 1 a 5 implementadas y probadas sin conexión a base de datos, incluida generación real de PDF con Chromium. La aplicación y validación del esquema en MariaDB siguen pendientes de un entorno autorizado; todavía no se considera completada la funcionalidad de extremo a extremo.
|
|
|
|
## Requisitos confirmados
|
|
|
|
- Las cotizaciones nuevas comienzan sin capítulos. Las antiguas siguen pudiéndose consultar, editar, duplicar y exportar sin convertirlas.
|
|
- Una cotización puede tener cero capítulos o uno o más capítulos. Si tiene capítulos, todos sus detalles pertenecen a uno de ellos, incluidos los bloques de texto.
|
|
- El capítulo tiene una cabecera donde solo se edita su descripción y un pie con su subtotal automático, sin campos editables.
|
|
- Un botón en la barra de herramientas de detalles permite añadir capítulos.
|
|
- Al mover un capítulo se desplazan todos sus detalles, conservando el orden interior.
|
|
- El PDF soporta ambos modos. Con capítulos se puede mostrar el desglose económico de los detalles o mostrar únicamente sus totales de capítulo, conservando las descripciones.
|
|
|
|
## Fase 1: decisiones funcionales
|
|
|
|
Las reglas de esta sección y de los apartados «Comportamiento del editor» e «Informe PDF» constituyen la especificación funcional de partida para las fases 2 a 6. Se concretan los valores propuestos en el plan de actuación; cualquier cambio posterior debe actualizar sus casos de aceptación.
|
|
|
|
### Modos, títulos e importes
|
|
|
|
- La cotización empieza en modo libre, sin capítulos. Leer, editar o exportar una cotización antigua no activa la agrupación.
|
|
- Crear el primer capítulo cambia toda la cotización al modo agrupado en una única operación: todos sus detalles, incluidos los textos, pasan al capítulo. No existen estados intermedios guardables con detalles huérfanos.
|
|
- Se permiten capítulos vacíos y capítulos que solo contienen texto. Ambos muestran subtotal de cero en la moneda de la cotización.
|
|
- La descripción del capítulo es obligatoria: se rechaza un valor vacío o compuesto solo por espacios al crear o guardar. Puede coincidir con la de otro capítulo; la identidad no depende del título. Cancelar el alta no modifica los detalles ni el modo.
|
|
- Los capítulos se numeran automáticamente desde 1 según su orden. La cabecera muestra «1. Descripción» y el pie «Subtotal capítulo 1. Descripción», con sus etiquetas traducidas. La numeración no es editable ni forma parte de la descripción guardada.
|
|
- La cabecera solo permite editar la descripción. Sus botones de selección, movimiento y acciones no representan campos de datos editables. El subtotal se muestra en el pie y no se repite como campo monetario editable en la cabecera.
|
|
- No se incluyen capítulos anidados. El plegado visual del editor se incorpora por petición posterior: los capítulos empiezan desplegados; el botón de flecha oculta sus detalles y acciones, conservando título y subtotal visibles. Este estado local se mantiene al reordenar el capítulo y no se guarda ni se envía al PDF. Los capítulos duplicados empiezan desplegados.
|
|
- El subtotal suma los totales de sus líneas tras sus descuentos individuales y antes del descuento general e impuestos. No hay descuentos, impuestos ni moneda propios del capítulo. Se conservan las reglas actuales de precisión y redondeo.
|
|
- Agrupar, desagrupar o mover contenido conserva los valores de las líneas y el total general. La visibilidad del PDF tampoco modifica importes ni los oculta en el editor.
|
|
|
|
### Capítulo activo y destinos
|
|
|
|
- Crear un capítulo, activar su cabecera o interactuar con uno de sus detalles lo convierte en el capítulo activo. La selección múltiple no cambia por sí sola ese destino.
|
|
- Añadir filas, artículos o bloques los inserta al final del capítulo activo; si no hay activo, al final del último capítulo. El selector de catálogo o bloques conserva el destino al abrirse, también si permite varias inserciones.
|
|
- Insertar encima o debajo de un detalle mantiene su capítulo, incluso si se trata de la primera o última línea. El pie no es un destino de inserción independiente.
|
|
- Eliminar el capítulo activo deja activo el capítulo anterior; si no existe, el siguiente. Al quitar todos los capítulos deja de haber capítulo activo.
|
|
|
|
### Selección, movimiento y acciones en grupo
|
|
|
|
- La cabecera selecciona el capítulo como unidad completa. El pie no se selecciona, edita, duplica, elimina ni arrastra por separado.
|
|
- Arrastrar un capítulo seleccionado entre varios capítulos seleccionados mueve esas unidades completas, manteniendo su orden relativo y el de sus detalles. El destino es un límite entre capítulos, nunca el interior de otro.
|
|
- Arrastrar detalles seleccionados los inserta consecutivamente en el destino y conserva su orden visual relativo. Si cambia el capítulo, cambia también su pertenencia. Se puede dejar vacío el capítulo de origen.
|
|
- Los destinos de detalle son posiciones dentro de un capítulo, incluido un capítulo vacío; no se permite soltar detalles antes de la primera cabecera ni fuera del último capítulo.
|
|
- Si la selección mezcla capítulos con detalles de otros capítulos, se deshabilita su arrastre conjunto y se explica el motivo. Los detalles pertenecientes a capítulos seleccionados ya están incluidos en esas unidades y no cuentan como selección mixta.
|
|
- Duplicar una selección coloca las copias de capítulos después del último capítulo seleccionado y las copias de detalles después del último detalle seleccionado de su respectivo capítulo. Se conserva el orden relativo. Los hijos de un capítulo seleccionado se procesan una sola vez.
|
|
- Duplicar capítulos copia títulos y contenido; la numeración se recalcula y las identidades son nuevas. Duplicar detalles mantiene su capítulo y los valores actuales del formulario, incluidos cambios aún no guardados.
|
|
- Borrar una selección con contenido requiere confirmación e indica los capítulos y detalles afectados, sin contar hijos dos veces. Borrar solo detalles conserva los capítulos, aunque queden vacíos.
|
|
- Las mismas restricciones de pertenencia y movimiento se aplican con ratón, teclado y pantalla táctil.
|
|
|
|
### Borrado y vuelta al modo libre
|
|
|
|
- Eliminar un capítulo vacío no requiere un diálogo adicional. Si era el último, la cotización vuelve al modo libre.
|
|
- Para eliminar un capítulo con contenido, el diálogo permite cancelar, eliminar capítulo y contenido o conservar el contenido trasladándolo a un capítulo no seleccionado para borrar. El traslado añade las líneas al final del destino, conservando el orden visual de origen.
|
|
- Si no queda otro capítulo de destino, la opción de conservación será «Quitar capítulos y conservar detalles»: elimina la agrupación completa y mantiene todas las líneas en su orden visual. Así se puede quitar el último capítulo sin perder contenido.
|
|
- La acción «Quitar todos los capítulos» está disponible siempre que haya capítulos y pide confirmación. Conserva todos los detalles y sus valores, elimina todas las pertenencias y vuelve al modo libre. Cancelar no produce cambios.
|
|
- Las operaciones afectan al formulario y marcan cambios sin guardar; solo el guardado las persiste. Cancelar un diálogo no modifica datos. Se mantiene el aviso existente al abandonar con cambios pendientes.
|
|
|
|
### Criterios de cierre de la fase
|
|
|
|
La fase 1 entrega reglas y escenarios para implementación, sin incorporar todavía código de dominio, interfaz ni persistencia. Los siguientes resultados quedan fijados:
|
|
|
|
| Caso | Acción | Resultado exigido |
|
|
| --- | --- | --- |
|
|
| F1-01 | Añadir el primer capítulo a tres detalles libres | Los tres pertenecen al nuevo capítulo en el mismo orden; no cambia el total |
|
|
| F1-02 | Cancelar el primer capítulo o introducir solo espacios | Cancelar conserva el modo libre; el título inválido impide completar el alta |
|
|
| F1-03 | Añadir un segundo capítulo y un artículo | El segundo se crea vacío y activo; el artículo se incorpora a él |
|
|
| F1-04 | Mover un capítulo con artículos y textos | Se mueve la unidad completa; la numeración se actualiza y sus detalles mantienen el orden |
|
|
| F1-05 | Mover el último detalle de un capítulo a otro vacío | El origen queda vacío con subtotal cero; el destino recalcula su subtotal; el total general no cambia |
|
|
| F1-06 | Borrar el último capítulo conservando contenido | Desaparece la agrupación y quedan todas las líneas libres con sus valores y orden |
|
|
| F1-07 | Duplicar o borrar un capítulo y alguno de sus hijos seleccionados | El hijo se procesa una sola vez; nunca queda una referencia huérfana |
|
|
| F1-08 | Ocultar importes individuales y guardar | Editor con precios visibles; PDF con cantidades, descripciones y subtotales de capítulo, sin precios ni descuentos individuales |
|
|
| F1-09 | Quitar capítulos, guardar y volver a agrupar | La preferencia PDF se conserva, se ignora en modo libre y vuelve a aplicarse en modo agrupado |
|
|
| F1-10 | Editar una cotización antigua sin añadir capítulos | Se mantiene el flujo libre y la plantilla de exportación sin capítulos |
|
|
|
|
Estos escenarios serán verificados mediante pruebas en las fases de implementación; su definición no implica que la aplicación ya los soporte.
|
|
|
|
## Auditoría inicial del código
|
|
|
|
Las rutas citadas son relativas a la raíz del repositorio. Esta tabla describe el estado previo a las fases de implementación; sus entregas se documentan al final.
|
|
|
|
| Área | Código actual | Consecuencia para los capítulos |
|
|
| --- | --- | --- |
|
|
| Especificación inicial | `doc/readme.adoc` exige conceptos libres y orden de los detalles; no define capítulos | La agrupación constituye una ampliación del modelo actual |
|
|
| Dominio | `server/src/contexts/sales/domain/entities/Quotes/Quote.ts`, `QuoteItem.ts` | `Quote` contiene una colección plana; `QuoteItem` contiene artículo, descripción, cantidad, precio y descuento. No hay pertenencia ni entidad capítulo |
|
|
| Cálculos | `QuoteItem.subtotalPrice/totalPrice`, `Quote._calculateTotalPriceItems`; `client/src/lib/calc.ts` | El subtotal de cotización suma los totales de línea después del descuento de línea; luego aplica descuento general e impuesto |
|
|
| DTOs | `shared/lib/contexts/sales/application/dto/Quote/` | Crear, obtener, actualizar y duplicar tienen contratos propios que habrá que extender de forma coherente |
|
|
| Validación | DTOs con Joi y `Quote.create` | Joi admite campos desconocidos; `Quote.create` tiene pendientes las reglas de negocio. Añadir campos al formulario no garantiza la integridad en servidor |
|
|
| Casos de uso | `server/src/contexts/sales/application/Quote/{CreateQuote,UpdateQuote,DuplicateQuote}.useCase.ts` | Crear y actualizar reconstruyen las líneas; duplicar crea nuevas entidades de detalle. Hay que transportar capítulos y reasignar sus referencias al duplicar |
|
|
| Persistencia | `server/src/contexts/sales/infrastructure/sequelize/{quote,quoteItem}.model.ts` | `quote_items` tiene clave compuesta `quote_id/item_id` y orden mediante `position`; no tiene capítulo |
|
|
| Repositorio y mapeo | `Quote.repository.ts`, `mappers/{quote,quoteItem}.mapper.ts` bajo la misma infraestructura | Obtener ordena por `position`; actualizar elimina y reinserta los detalles en una transacción. Los capítulos necesitan formar parte de esa misma escritura atómica |
|
|
| Presentación API | Presentadores de `createQuote`, `getQuote`, `updateQuote`, `duplicateQuote` | Cada uno mapea sus propias líneas. Es necesario cubrir todos; no basta con modificar GET |
|
|
| Alta SPA | `client/src/app/quotes/create.tsx` | Recoge datos generales y redirige al editor; no requiere introducir capítulos en el alta |
|
|
| Editor | `client/src/app/quotes/edit.tsx`, `components/editors/QuoteDetailsCardEditor.tsx` | React Hook Form usa `items` como lista plana mediante `useFieldArray`; recalcula importes al cambiar campos o estructura |
|
|
| Tabla y acciones | `QuoteItemsSortableDataTable.tsx`, `QuoteItemsSortableDataTableToolbar.tsx`, `QuoteItemsInlineRowActions.tsx` | TanStack Table y dnd-kit mueven filas e incluyen selección múltiple, inserción, duplicado y borrado. Todas esas operaciones necesitan respetar pertenencia |
|
|
| Artículos y textos | Selectores de catálogo y bloques en `QuoteDetailsCardEditor.tsx` | Actualmente añaden al final de la lista; en modo capítulos deben tener un capítulo de destino |
|
|
| Proveedor de datos | `client/src/app/quotes/hooks/useQuotes.tsx` | Las operaciones actuales pueden seguir usando `useDataSource` y las claves existentes; no hacen falta llamadas HTTP desde componentes |
|
|
| PDF | `ReportQuote.controller.ts`, `reporter/ReportQuote.reporter.ts`, `reporter/templates/quote/template.hbs` | La ruta `GET /quotes/:quoteId/report` carga la cotización guardada; Handlebars recorre `items` y Puppeteer genera el PDF |
|
|
| Preferencias de informe | `show_line_discount_column` atraviesa dominio, DTOs, modelo, editor y plantilla | Existe un patrón cercano para persistir la nueva preferencia de visibilidad de importes |
|
|
| Traducciones | `GetQuote.presenter.ts`, `resolveLocalizedQuoteDescriptions.ts` y reporter | Las descripciones de catálogo tienen proyección por idioma; no debe alterarse esa lógica al agrupar ni traducirse el título libre como si fuera un artículo |
|
|
|
|
Las rutas de cotizaciones ya usan `checkUser`, `getDealerMiddleware` y `handleRequest`. Se extenderán los contratos existentes manteniendo estos controles, sin crear endpoints independientes para editar capítulos.
|
|
|
|
## Modelo propuesto
|
|
|
|
Mantener `items` como lista de detalles y añadir una colección `chapters` al agregado `Quote`. Cada capítulo tendrá identidad propia y descripción. Cada detalle incorporará `chapter_id`, nullable en el modo sin capítulos. La pertenencia será explícita, no inferida por encontrar una cabecera anterior.
|
|
|
|
Contrato orientativo, adicional a los campos actuales:
|
|
|
|
```ts
|
|
chapters: Array<{ id: string; description: string }>;
|
|
items: Array<ExistingQuoteItem & { chapter_id: string | null }>;
|
|
show_chapter_line_amounts: boolean;
|
|
```
|
|
|
|
- El orden de `chapters` define el orden exterior. El orden de `items` define el orden de los detalles dentro de cada capítulo; serializar la lista plana en orden de capítulos y después de detalles.
|
|
- El servidor devuelve además el subtotal calculado por capítulo en el DTO de respuesta. El request no acepta ese subtotal como fuente de verdad.
|
|
- No guardar el pie como `QuoteItem`: produciría una línea monetaria adicional y permitiría duplicar, mover o editar un subtotal como un concepto normal.
|
|
- No añadir un booleano `has_chapters`: se deriva de la colección y evita estados contradictorios.
|
|
- El identificador del capítulo debe ser estable entre cargas y guardados. Diferenciarlo de los identificadores internos que genera `useFieldArray`; no usar su `id` interno como identidad persistente.
|
|
- No es necesario cambiar la identidad pública de los detalles para esta funcionalidad; hoy los DTOs no exponen `item_id` y actualizar reconstruye las entidades.
|
|
|
|
### Persistencia propuesta
|
|
|
|
- Nueva tabla `quote_chapters`: `quote_id`, `chapter_id`, `position`, `description`. Clave compuesta por cotización y capítulo, con borrado en cascada desde la cotización.
|
|
- `quote_items.chapter_id` nullable e indexado junto con `quote_id`. La referencia debe apuntar al capítulo de la misma cotización; considerar FK compuesta además de la validación de dominio.
|
|
- `quotes.show_chapter_line_amounts`: booleano con valor por defecto `true`.
|
|
- Persistir posiciones a partir de los arrays, como ya hace el mapper de detalles; no pedirlas también en el request.
|
|
- Cargar capítulos y detalles con orden explícito por posición. Mantener la consulta de listado sin cargar asociaciones innecesarias.
|
|
- Crear y actualizar cabecera, capítulos y detalles en la misma transacción. Con el reemplazo actual, eliminar primero detalles, después capítulos, insertar capítulos y finalmente detalles; no ejecutar en paralelo operaciones dependientes de las FK.
|
|
- Los registros históricos conservan `chapter_id = null` y ningún capítulo. No crear capítulos automáticamente al leerlos.
|
|
|
|
La preparación y aplicación del cambio de esquema será una tarea de implementación/despliegue expresamente autorizada. Esta auditoría no ejecuta migraciones ni arranca el backend. El arranque actual usa `sync({ alter: true })`: antes de desplegar hay que revisar su efecto sobre la nueva tabla, columnas, asociaciones y FK en una base aislada. No dar por validado el esquema solo porque TypeScript compile.
|
|
|
|
## Reglas de dominio y compatibilidad
|
|
|
|
1. Con `chapters.length === 0`, todos los detalles tienen `chapter_id = null`.
|
|
2. Con uno o más capítulos, cada detalle tiene exactamente un `chapter_id` existente en esa misma cotización. Rechazar referencias desconocidas, capítulos duplicados y pertenencias a otra cotización.
|
|
3. No hay capítulos anidados en esta primera versión. La descripción es el único dato editable de la cabecera; la numeración, si se muestra, se deriva de la posición.
|
|
4. El subtotal de capítulo suma el `totalPrice` de sus detalles, con descuento de línea aplicado, antes del descuento general y los impuestos. Usar `MoneyValue` y las mismas escalas y reglas de redondeo que en los cálculos actuales.
|
|
5. Los textos sin importe no contribuyen al subtotal. Los títulos y pies nunca contribuyen al total de cotización.
|
|
6. Agrupar, desagrupar, cambiar el orden o alternar la visibilidad del informe no cambia el total general.
|
|
7. Validar estructura en DTO/Joi y pertenencia en el agregado antes de persistir. Devolver errores con el patrón actual, sin corregir silenciosamente una estructura inválida enviada por API.
|
|
8. En creación y lectura antigua, campos nuevos ausentes equivalen a `chapters: []`, `chapter_id: null` y visibilidad `true`.
|
|
9. Para PUT antiguos sobre cotizaciones sin capítulos, aceptar que falten los nuevos campos. Para una cotización ya agrupada, rechazar un PUT que omita la estructura de capítulos: el cliente antiguo no conoce la agrupación y podría perderla. La eliminación de agrupación debe enviar explícitamente `chapters: []` y todos los detalles sin pertenencia. La preferencia de informe omitida en actualización conserva el valor guardado.
|
|
10. Duplicar una cotización o un capítulo crea nuevos identificadores de capítulo y remapea los detalles; conserva orden, descripciones y preferencia PDF.
|
|
|
|
La compatibilidad se refiere a conservar datos y flujos antiguos; no a permitir que un cliente antiguo elimine accidentalmente capítulos que no puede representar.
|
|
|
|
## Comportamiento del editor
|
|
|
|
Estas decisiones quedan fijadas en la fase 1 y se complementan con las reglas de selección, destinos y borrado anteriores:
|
|
|
|
| Operación | Comportamiento definido |
|
|
| --- | --- |
|
|
| Añadir el primer capítulo | Solicitar descripción y agrupar en él todos los detalles existentes, conservando su orden. En una cotización vacía, crear un capítulo vacío |
|
|
| Añadir otro capítulo | Crear un capítulo vacío después del activo, o al final si no hay capítulo activo; llevar el foco a su descripción |
|
|
| Añadir fila, artículo o bloque | Insertar en el capítulo activo; si no hay activo, usar el último. En modo sin capítulos mantener el comportamiento actual |
|
|
| Insertar encima o debajo de un detalle | Mantener el capítulo del detalle de referencia, también en sus extremos |
|
|
| Mover capítulo | Moverlo como unidad con todos sus detalles. Solo aceptar destinos entre capítulos; conservar el orden interior |
|
|
| Mover detalles | Reordenar dentro de su capítulo o trasladar a otro, actualizando pertenencia y orden en una operación. Incluir destino en capítulos vacíos; no permitir soltarlos fuera de capítulo |
|
|
| Seleccionar capítulo | Seleccionar su unidad completa para acciones de grupo. Si también se seleccionaron hijos, procesarlos una sola vez |
|
|
| Selección múltiple de detalles | Mantener la operación actual; en un traslado entre capítulos, todos los seleccionados pasan al destino conservando orden relativo |
|
|
| Selección mixta de capítulos y detalles sueltos | Deshabilitar el arrastre mixto inicialmente con explicación traducida; permite evitar movimientos ambiguos sin quitar la selección múltiple de detalles |
|
|
| Duplicar detalle | Duplicarlo en el mismo capítulo usando los valores actuales del formulario |
|
|
| Duplicar capítulo | Duplicar cabecera y contenido completo con nueva identidad y pertenencias remapeadas |
|
|
| Eliminar capítulo vacío | Eliminarlo; si era el último, volver al modo sin capítulos |
|
|
| Eliminar capítulo con contenido | Confirmar eliminación de contenido o trasladarlo a otro capítulo; sin destino disponible, permitir quitar la agrupación conservando detalles. Nunca dejar detalles huérfanos |
|
|
| Quitar todos los capítulos | Acción explícita que conserva los detalles y su orden visual, elimina sus referencias y vuelve al modo libre; también disponible con un único capítulo |
|
|
|
|
La tabla mostrará una cabecera diferenciada, detalles y pie por capítulo, siguiendo la referencia visual. Solo la descripción de la cabecera tendrá control de edición; el pie será texto monetario calculado y no tendrá selección, arrastre ni menú de acciones.
|
|
|
|
Reutilizar las celdas actuales para detalles, los diálogos y botones existentes y dnd-kit. Generar una proyección visual de cabeceras/detalles/pies a partir de los datos; esa proyección no se envía como `items` ni se incorpora al cálculo general. Mantener correspondencia explícita entre índices visuales y los índices de `items` del formulario.
|
|
|
|
Centralizar las operaciones de agrupación en el feature de cotizaciones, reutilizando el formulario y los hooks actuales. Los subtotales deben reaccionar a cantidades, precios, descuentos, altas, bajas y traslados; leer valores observados del formulario, no solo `fields`, que no refleja por sí solo cada edición de celda. El guardado enviará capítulos y pertenencias junto al resto de la cotización. Mantener aviso de cambios sin guardar.
|
|
|
|
## Informe PDF
|
|
|
|
Decisión de fase 1: la visibilidad de importes es una preferencia global por cotización, no por capítulo ni por descarga. Se guardará como `show_chapter_line_amounts`, siguiendo el patrón de `show_line_discount_column`. Un control en el editor permitirá «Mostrar importes de los detalles en el PDF» cuando haya capítulos. Valor inicial `true`, independiente de la visibilidad de descuentos y sin ocultar los importes en el editor. Quitar capítulos conserva esta preferencia para una agrupación posterior; duplicar la cotización también la conserva.
|
|
|
|
- Sin capítulos: conservar el informe actual e ignorar esta preferencia.
|
|
- Con capítulos y desglose: mostrar cabecera, detalles con sus importes y pie con subtotal.
|
|
- Con capítulos sin desglose: mostrar cantidades y descripciones de detalle, ocultando precio unitario, subtotal, porcentaje de descuento e importe total de cada detalle; mostrar el subtotal del capítulo. Evitar columnas monetarias vacías y ajustar `colspan` y cabeceras. El descuento general, los impuestos y el total de la cotización siguen en el resumen final.
|
|
- El resumen de descuentos de línea existente se mantiene como dato global según `show_line_discount_column`; nunca debe revelar precios ni porcentajes de detalles individuales cuando se oculte el desglose.
|
|
- Usar el mismo cálculo de capítulo en API y reporter, sin sumar cadenas formateadas ni confiar en subtotales enviados desde el navegador.
|
|
- Adaptar `ReportQuote.reporter.ts` y la plantilla única `template.hbs` para ambas estructuras. No duplicar plantillas completas.
|
|
- Añadir etiquetas de capítulo y subtotal a los idiomas documentales reales (`es`, `en`, `fr`). Los títulos libres se imprimen como texto escapado; conservar la resolución localizada de artículos existente.
|
|
- La preferencia persistida llega tanto a previsualización como a descarga mediante la ruta actual; no es necesario añadir parámetros de URL. La previsualización actual usa datos guardados, por lo que el cambio requiere guardar para reflejarse en el informe.
|
|
- Verificar PDF de varias páginas: cabecera junto al primer detalle cuando quepa, pie junto al último, cabeceras de tabla repetidas y capítulos largos capaces de partirse. No aplicar `break-inside: avoid` al capítulo completo.
|
|
|
|
Las cantidades y descripciones permanecen visibles en ambos modos del PDF. Con desglose, la columna de descuentos depende de `show_line_discount_column`; sin desglose, no se muestran descuentos individuales aunque esa preferencia esté activada. La cabecera del capítulo muestra número y descripción y su pie muestra el subtotal, tanto con desglose como sin él.
|
|
|
|
## Secuencia de implementación
|
|
|
|
1. **Reglas funcionales — completada.** Modos libre/agrupado, títulos, subtotales, destinos, selección, movimientos, duplicado, borrado y preferencia PDF definidos en este documento, con escenarios F1-01 a F1-10.
|
|
2. **Dominio y contratos compartidos — completada.** `QuoteChapter`, pertenencia y preferencia incorporados al dominio; DTOs y validadores ampliados. Presentadores de las cuatro respuestas adaptados al contrato. Pruebas de invariantes, subtotales, compatibilidad antigua y respuestas API.
|
|
3. **Backend y persistencia — implementación completada.** Modelos, asociaciones, mappers, repositorio transaccional y casos de uso adaptados. Lectura/escritura, rechazo de estructura inválida y duplicado con remapeo probados con dobles. Cambio de esquema preparado en `doc/capitulos-esquema.md`, sin aplicación a una base existente ni certificación de integración MariaDB.
|
|
4. **Editor — implementación completada.** Botón de alta, cabecera editable, pie calculado, destinos de inserción y movimiento de capítulos/detalles. Selección, duplicado, borrado, traslado y vuelta al modo libre cubiertos mediante pruebas del formulario, con sensores de arrastre simulados. Pendiente la inspección en navegador y la integración con una API/base autorizadas.
|
|
5. **PDF — completada.** Preferencia guardada, proyección del reporter, plantilla de ambos modos y traducciones. Pruebas HTML de contenido y ausencia de importes individuales, más generación e inspección de PDF multipágina con recursos locales. La validación integrada con datos guardados en MariaDB sigue en fase 6.
|
|
6. **Regresión y entrega.** Tests acotados, lint y compilación de paquetes afectados. Con entorno aislado autorizado, verificar esquema y ampliar E2E para crear, guardar, recargar, reordenar, duplicar y exportar ambos modos. Desplegar esquema/backend compatibles antes del cliente y evitar revertir a un backend que ignore capítulos después de empezar a guardarlos.
|
|
|
|
Cada etapa tendrá pruebas de su comportamiento; la funcionalidad no se considera completa hasta cubrir editor, API, persistencia y exportación conjuntamente.
|
|
|
|
## Casos de aceptación prioritarios
|
|
|
|
- Cotización antigua: abrir, editar, guardar, duplicar y exportar conserva líneas, orden y totales, sin capítulos creados automáticamente.
|
|
- Cotización nueva: comienza sin capítulos y permite seguir trabajando de ese modo.
|
|
- Crear primer capítulo con detalles existentes no pierde ninguna línea ni deja conceptos fuera.
|
|
- Dos capítulos con artículos y bloques de texto: guardado y recarga conservan agrupación, descripciones y orden.
|
|
- Editar cantidad, precio o descuento actualiza pie y resumen; mover detalles recalcula ambos capítulos sin cambiar el total general.
|
|
- Ejemplo: líneas con total 90 € y 50 € dan capítulo de 140 €; otra línea de 60 € da capítulo de 60 €. Subtotal general de 200 €; descuento general del 10 % e impuesto del 21 % producen 217,80 €. Reordenar u ocultar precios mantiene estos resultados.
|
|
- Capítulo vacío: pie de 0 en la moneda de la cotización; capítulo solo con textos: subtotal de 0, sin inventar importes para los textos.
|
|
- Arrastre por ratón, táctil y teclado mueve el capítulo completo; arrastre de detalles entre capítulos y hacia capítulo vacío mantiene pertenencia válida.
|
|
- Duplicado y borrado individual o múltiple no procesan hijos dos veces, no reutilizan identidades de capítulo y no dejan huérfanos.
|
|
- La API rechaza pertenencia desconocida, de otra cotización, capítulos duplicados y combinación de detalles libres con capítulos; una actualización inválida no cambia datos persistidos.
|
|
- Un fallo al escribir capítulos o detalles revierte toda la transacción.
|
|
- PDF sin capítulos y PDF con capítulos en ambos modos; combinar con descuentos visibles/ocultos, moneda adicional e idiomas documentales. Probar líneas con precios distintivos para verificar su ausencia real en el HTML sin desglose.
|
|
- PDF largo: ningún contenido desaparece; pies y encabezados quedan legibles tras los saltos de página.
|
|
|
|
## Validación realizada y hallazgos previos
|
|
|
|
### Integración MariaDB: preparación y acceso pendiente
|
|
|
|
- Nueva suite explícita `cd server && yarn test:mariadb`, separada de los tests habituales. Catorce casos preparados para esquema histórico, guardado/recarga, presentador API/HTML, movimiento, duplicado, desagrupación, rollback real mediante triggers, bloqueo concurrente, segundo arranque y FK compuesta.
|
|
- El fixture exige MariaDB y acceso específico por variables `UECKO_MARIADB_TEST_*`. Crea y elimina únicamente un esquema nuevo aleatorio con datos ficticios, sin usar la configuración ni las tablas de la aplicación. Seis pruebas unitarias de seguridad del fixture.
|
|
- No se da por completada esta fase: el acceso facilitado a `localhost:3306` funciona, pero el servidor se identifica como MySQL 8.0.42. La suite rechaza ese motor antes del DDL y deja intacta `uecko`. Falta un destino MariaDB o autorización para cambiar el motor de validación. Procedimiento, permisos y límites en `doc/capitulos-esquema.md`.
|
|
|
|
### Entrega de fase 5
|
|
|
|
- `ReportQuote.reporter.ts`: proyección ordenada de cabeceras, detalles y pies. Los subtotales proceden de `Quote.getChapterSubtotal`, después del descuento de línea y antes del descuento general e impuestos. Sin capítulos se ignora `show_chapter_line_amounts`. Con capítulos y preferencia desactivada, los importes individuales tampoco se incorporan a la proyección del informe.
|
|
- `template.hbs`: una sola tabla para ambos modos. Cabeceras numeradas, pies con subtotal y texto escapado; modo sin desglose con únicamente cantidad y descripción, sin columnas económicas vacías. El resumen conserva descuentos globales, impuestos y moneda adicional. Se mantienen las traducciones de artículos y sus descripciones de respaldo.
|
|
- Etiqueta de subtotal de capítulo en los idiomas documentales `es`, `en` y `fr`, incluida en la validación de etiquetas requeridas. No se amplía el selector de idiomas documentales.
|
|
- Impresión: cabecera de tabla repetida, títulos unidos al primer detalle y pies al último cuando caben; capítulos largos y bloques de texto pueden continuar en otras páginas. Colores de capítulo preservados al imprimir, importes sin partir y resumen económico sin división entre páginas cuando cabe.
|
|
- Correcciones necesarias en el reporter: eliminar la espera de navegación tras `setContent` y cerrar Chromium en `finally`; excluir líneas de texto del acumulador de descuentos para evitar mezclar monedas nulas; imprimir el porcentaje de descuento general como cadena formateada y mostrar la moneda base real en la leyenda de cambio, que antes decía siempre `1 EUR`.
|
|
- Pruebas automatizadas: compatibilidad sin capítulos, visibilidad de importes y descuentos, orden y subtotales, capítulos vacíos y de texto, EUR/USD y moneda adicional, idiomas, escape de títulos/descripciones, traducción de artículos y cierre del navegador tanto en éxito como en fallo.
|
|
- Validación: `cd server && yarn test --runInBand src/contexts/sales` (10 suites, 72 pruebas), `yarn tsc --noEmit --pretty false`, ESLint acotado al reporter y sus pruebas y `git diff --check`. ESLint usa rutas de parser corregidas por argumento para producción y `project: null` para tests, excluidos por el tsconfig heredado; los tests sí comprueban tipos mediante ts-jest. No se cambia la configuración de lint en esta fase.
|
|
- PDF reales generados mediante `ReportQuotePresenter.toPDF`: sin capítulos (1 página), con desglose (1), sin desglose (1), capítulo de 90 líneas en ambos modos (9 páginas por modo), doce capítulos en límites de página (7) y bloque de texto de 150 líneas (5). Comprobación de texto con `pdftotext`: todas las líneas una sola vez, cabeceras repetidas en páginas con detalles, doce títulos junto al primer detalle y doce pies junto al último. Inspección visual de la muestra con desglose y de la última página con varios capítulos.
|
|
- Muestras temporales: `/tmp/uecko-chapter-pdf/`, generadas sin iniciar la API ni conectar a MariaDB. Para una verificación reproducible sin red se sustituyeron las peticiones al CSS externo por el CSS Tailwind ya compilado en `dist/client` y la imagen del pie por su copia local; no se modificó `dist`. La plantilla de producción conserva su dependencia previa del CDN de Tailwind 2 y del pie remoto; estas muestras no certifican la disponibilidad de esos recursos ni equivalencia visual exacta con ese CDN.
|
|
- No se ejecutan E2E contra la aplicación, operaciones de base de datos/Docker, instalaciones ni builds de distribución. El guardado real y la integración completa se reservan para fase 6 tras aplicar y comprobar el esquema en un entorno autorizado. Persiste una incoherencia previa ajena a capítulos: el bloque de condiciones usa `quote.default_legal_terms`, aunque el reporter expone `dealer.default_legal_terms`; no se modifica en esta fase.
|
|
|
|
### Entrega de fase 4
|
|
|
|
- Nuevo `client/src/app/quotes/chapterEditor.ts`: operaciones sobre capítulos y detalles, orden, duplicado sin doble procesamiento de hijos, traslado, borrado, desagrupación y cálculo de pies con `MoneyValue`. El guardado elimina de los capítulos sus subtotales de respuesta y de las líneas su descripción localizada de solo lectura.
|
|
- Nuevo hook `useChapterEditor.ts`: lectura de valores actuales de React Hook Form, capítulo activo, destino capturado al abrir selectores, aplicación de cambios con aviso de formulario modificado y foco del título al crear un capítulo. Los identificadores persistidos del capítulo no se confunden con los internos de `useFieldArray`.
|
|
- Nuevos `QuoteChapterCreateDialog.tsx` y `QuoteChaptersDataTable.tsx`: alta con título obligatorio, cabeceras numeradas, detalles con las celdas existentes, pies no editables, arrastre de unidades completas y detalles hacia capítulos vacíos o con contenido, selección, duplicado y diálogos de borrado/traslado. La selección mixta deshabilita el arrastre conjunto y evita procesar dos veces los hijos de un capítulo seleccionado.
|
|
- `QuoteDetailsCardEditor.tsx` integra ambos modos y conserva la tabla anterior para cotizaciones sin capítulos. El botón «Añadir capítulo» aparece en su barra de herramientas. Filas nuevas, artículos y bloques respetan el capítulo activo o el destino capturado al abrir el selector.
|
|
- La barra de herramientas, acciones de fila, filas arrastrables y `useDetailColumns.tsx` reutilizan los componentes existentes. Se ha corregido el evento de selección de Radix Checkbox; las columnas conservan sus celdas durante la escritura y se actualizan cuando cambia la moneda o el idioma.
|
|
- `edit.tsx` conserva defaults compatibles, serializa la estructura y guarda la preferencia PDF. El watcher se suscribe desde el inicio para recalcular también la primera modificación; subtotal y descuento se comparan con sus campos correspondientes. Al cargar se recupera la moneda/idioma reales y al cambiar la moneda se mantienen los valores numéricos de las líneas bajo la nueva moneda, sin añadir conversión de divisas.
|
|
- `client/src/lib/calc.ts` conserva la moneda de las líneas y de la cotización y excluye importes nulos, manteniendo el criterio del backend para líneas incompletas y textos. Los pies no entran en el cálculo general.
|
|
- Nuevos textos en `es`, `en`, `ca` y `fr`: títulos, acciones, confirmaciones, preferencia PDF y mensajes de accesibilidad del arrastre.
|
|
- La preferencia «Mostrar importes de los detalles en el PDF» queda guardada, pero su efecto en la plantilla pertenece a la fase 5. No se ha modificado todavía el reporter ni la plantilla.
|
|
|
|
Validaciones de fase 4:
|
|
|
|
- Seis suites del editor y operaciones: 27 pruebas correctas, incluidas 12 pruebas de integración React Hook Form con componentes reales de capítulos. Cubren creación/cancelación, título obligatorio, subtotales en vivo y mantenimiento del foco, movimiento de bloques, traslado a capítulo vacío, duplicado de selección, inserción en extremos, borrado/traslado, regreso al modo libre, preferencia y payload compatible con el validador compartido. El arrastre, sus destinos de teclado y sus colisiones se simulan; no son E2E en navegador.
|
|
- Traducciones: seis comprobaciones correctas de claves e interpolaciones, incluidas las de capítulos en catalán. La suite completa de recursos se ejecutó y conserva un fallo previo de bloques franceses: falta `CAP_SEP_AMBIENTES` y aparecen `CAP_INSTALACION`/`CAP_SUMINISTRO` respecto a español e inglés. Esos ficheros de bloques no se modifican en esta fase. La ejecución acotada de claves/textos excluye las dos comprobaciones de bloques.
|
|
- `cd client && yarn tsc --noEmit --pretty false` correcto. ESLint acotado sin errores; permanecen advertencias del código anterior por `any` y contexto exportado junto a componentes. La configuración tenía una severidad inválida `"none"`: se cambia por `"off"` en `client/.eslintrc.cjs` para poder ejecutar ESLint.
|
|
- Se sustituyó el import de `FormLabel` desde el barrel global en la barra por `Label` de UI, con su asociación al switch. Esto evita cargar indirectamente `react-pdf` en su prueba y resuelve el fallo previo de Jest con `pdfjs-dist` en esa suite.
|
|
- No se instalaron dependencias: los tests de formulario usan React DOM, porque falta `@testing-library/dom` en el entorno. No se ejecutaron E2E ni inspección visual: el navegador Chromium de Playwright no está instalado. No se arrancó el backend ni se aplicó el esquema; el guardado/recarga se comprueba mediante payload, validador y reset del formulario, junto a las pruebas de persistencia de fase 3.
|
|
- No se ejecutó el build de distribución para evitar recrear `dist/client`; se comprobó TypeScript sin emitir. Tampoco se ejecutó lint global con `--max-warnings 0`, dado el historial de advertencias fuera de estos cambios.
|
|
|
|
### Entrega de fase 3
|
|
|
|
- Modelos `quote.model.ts`, `quoteItem.model.ts` y nuevo `quoteChapter.model.ts`: capítulos asociados a cotización, posiciones, pertenencia nullable e índice de detalles, preferencia PDF con default `true`. Barrel de modelos actualizado.
|
|
- Nuevo `quoteChapter.mapper.ts` y ampliación de mappers de cotización y detalle: lectura de capítulos ordenados, validación del propietario, pertenencias y preferencia. La escritura genera posiciones consecutivas y orden canónico por capítulos, conservando el orden interior de detalles y textos.
|
|
- `Quote.repository.ts`: creación y reemplazo usan una transacción obligatoria; guardan cabecera, eliminan detalles antes que capítulos e insertan capítulos antes que detalles. La lectura carga ambas asociaciones por separado y con orden explícito; la búsqueda de última cotización conserva también capítulos y transacción.
|
|
- Crear y actualizar reconstruyen entidades de capítulo mediante `createQuoteChapters.ts`, validan la estructura incluso al invocarse sin controlador y asignan pertenencias a las líneas. Los casos antiguos sin capítulos siguen siendo válidos.
|
|
- La actualización carga la cotización con bloqueo `FOR UPDATE` dentro de la transacción de escritura; rechaza omitir capítulos si el registro ya está agrupado. La desagrupación exige una estructura explícita sin pertenencias. Omitir la preferencia PDF mantiene el valor persistido.
|
|
- Duplicar genera nuevos capítulos y detalles, reasigna pertenencias a la cotización nueva y conserva títulos, orden, importes y preferencia PDF. Se comprueba el fallo del resultado de dominio antes de persistir la copia.
|
|
- Pruebas de fase 3: casos de uso de creación, consulta, actualización y duplicado; mapeo en ambos sentidos; reemplazo y creación transaccionales; propagación de fallos en capítulos y detalles; metadatos y asociaciones Sequelize. El rollback se verifica con un doble transaccional y la clase de transacción existente, sin conexión a MariaDB.
|
|
- Validación: `cd server && yarn test --runInBand src/contexts/sales` (9 suites, 56 pruebas); `cd shared && yarn test --runInBand --runTestsByPath lib/contexts/sales/application/dto/Quote/QuoteChapter.dto.test.ts` (27 pruebas). TypeScript del servidor con `yarn tsc --noEmit --pretty false`; ESLint acotado a los archivos ejecutables de fase 3 usando las rutas de configuración corregidas por argumento, sin cambiar la configuración del repositorio.
|
|
- No se ejecutan backend, Docker, DDL, migraciones, instalaciones, builds de distribución ni E2E. La comprobación de esquema, FK compuesta, bloqueo y rollback real permanece documentada en `doc/capitulos-esquema.md` para integración autorizada. No hay cambios nuevos en la interfaz que requieran repetir pruebas del cliente en esta fase.
|
|
- Diferencia relevante de infraestructura: Sequelize no instala la FK compuesta de pertenencia desde los modelos definidos; la validación de aplicación sí está implementada. La restricción SQL y su convivencia con `sync({ alter: true })` quedan preparadas para revisión explícita antes de aplicar el esquema.
|
|
|
|
### Entrega de fase 2
|
|
|
|
- Dominio: nueva entidad `QuoteChapter`, con identidad, cotización propietaria y descripción obligatoria; `QuoteItem.chapterId`; `Quote.chapters`, `showChapterLineAmounts` y `getChapterSubtotal`. Se rechazan duplicados, capítulos de otra cotización y pertenencias inválidas. Sin capítulos se conservan los valores por defecto antiguos.
|
|
- Contratos: `QuoteChapter.dto.ts`, DTOs de crear, consultar, actualizar y duplicar y sus barrel exports. Los requests admiten omitir los campos para compatibilidad y no introducen defaults que confundan omisión con desagrupación explícita. Las respuestas incluyen capítulos con subtotal calculado, pertenencia nullable y preferencia PDF.
|
|
- Validadores de crear/actualizar: verifican estructura, UUID v4, títulos, unicidad, pertenencia y preferencia booleana. Rechazan datos de subtotal o anidación dentro de capítulos enviados como entrada. La comprobación de PUT antiguo frente a una cotización ya agrupada requiere cargar el registro y corresponde a la fase 3.
|
|
- Los cuatro presentadores API se adaptan ahora para satisfacer los contratos de respuesta requeridos. No hay cambios de esquema, repositorio ni casos de uso: estos todavía no reconstruyen ni guardan capítulos. Esta fase no constituye una versión desplegable de la funcionalidad completa.
|
|
- Los textos sin importe se excluyen de la suma general, al igual que de la suma de capítulos. Esto evita intentar sumar importes nulos en la moneda por defecto a una cotización de otra moneda.
|
|
- Tests de contratos en shared: 27 pruebas correctas. Tests de dominio y regresión en servidor: 24 pruebas correctas; tests de los cuatro presentadores: 8 pruebas correctas.
|
|
- TypeScript de servidor y cliente: `yarn tsc --noEmit --pretty false` correcto en ambos paquetes.
|
|
- ESLint acotado a los archivos ejecutables modificados del servidor: sin errores y con tres advertencias previas por parámetros `context` sin usar en presentadores. Para ejecutarlo se usaron `--ignore-path ../.gitignore` y un `tsconfigRootDir` absoluto mediante `--parser-options`: la configuración habitual intenta leer rutas relativas inexistentes desde `server/`. No se modifica esa configuración fuera del alcance.
|
|
- El test nuevo de shared importa las funciones de `@jest/globals` explícitamente: en este entorno `shared/node_modules/@types` no existe y el `typeRoots` del paquete impide resolver los globales Jest automáticamente. No se instalaron dependencias ni se cambió la configuración global de pruebas.
|
|
- `git diff --check` correcto. No se ejecutan builds de distribución, lint global, E2E ni operaciones de infraestructura: se comprueban tipos sin emitir y pruebas acotadas; la validación de esquema y flujos completos corresponde a fases posteriores.
|
|
|
|
### Auditoría y fase 1
|
|
|
|
Validación del cierre de fase 1: revisión de coherencia entre decisiones funcionales, tabla del editor, comportamiento PDF y escenarios F1-01 a F1-10. `git diff --no-index --check /dev/null doc/capitulos-cotizaciones.md` correcto. No se repiten tests, lint, builds ni E2E porque esta fase solo modifica la especificación; los resultados siguientes corresponden a la auditoría inicial y no verifican capítulos implementados.
|
|
|
|
Estado inicial: `git status --short` limpio. Auditoría estática de especificación, rutas, dominio, DTOs, persistencia, editor, cálculos, traducciones y exportación.
|
|
|
|
Comandos ejecutados desde cada paquete:
|
|
|
|
```bash
|
|
# server
|
|
yarn test --runInBand --runTestsByPath src/contexts/sales/application/Quote/UpdateQuote.useCase.test.ts src/contexts/sales/infrastructure/mappers/quote.mapper.test.ts src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/ReportQuote.reporter.test.ts
|
|
|
|
# client
|
|
yarn test --runInBand --runTestsByPath src/app/quotes/components/QuoteItemsSortableDataTable.test.tsx src/app/quotes/components/QuoteItemsSortableDataTableToolbar.test.tsx src/app/quotes/components/QuoteItemsInlineRowActions.test.tsx
|
|
```
|
|
|
|
- Servidor: 3 suites y 12 pruebas correctas. Son pruebas con dobles de repositorio/mappers y renderizado HTML; no validan una base real ni generan PDF.
|
|
- Cliente: 2 suites correctas, 4 pruebas correctas; la suite de la barra de herramientas falla antes de ejecutar sus pruebas porque Jest no transforma el ESM de `pdfjs-dist`, importado indirectamente desde el barrel de componentes y `react-pdf`. Fallo previo a esta propuesta; no se ha modificado su configuración.
|
|
- No se ejecutaron builds ni lint: la entrega solo incorpora documentación y no cambia código ejecutable. No se ejecutó la batería completa.
|
|
- No se ejecutaron E2E, backend, Docker ni operaciones de base de datos: requieren infraestructura fuera del alcance de esta auditoría. No se instaló ninguna dependencia.
|
|
|
|
Inconsistencias observadas, sin cambios fuera del alcance:
|
|
|
|
- `AGENTS.md` indica que no se localizaron tests de servidor ni E2E; el repositorio actual sí contiene ambos y scripts Playwright en el paquete raíz.
|
|
- La interfaz carga `es/en/ca/fr`, mientras los selectores de idioma documental y el reporter admiten `es/en/fr`. Añadir textos de interfaz en los cuatro recursos; no asumir PDF catalán implementado.
|
|
- Los DTOs de línea tienen diferencias previas: crear declara `price`, actualizar/obtener usan `subtotal_price`, y el validador de actualización menciona `article_id` mientras el contrato y caso de uso usan `id_article`. Ampliar las pruebas de contrato sin normalizar campos ajenos a esta funcionalidad.
|
|
- El watcher de `edit.tsx` compara en varios puntos el subtotal o descuento calculado con `formData.total_price` en lugar del campo correspondiente. Revisar esa ruta al incorporar el recálculo de capítulos para no añadir escrituras redundantes; no se corrige en esta auditoría.
|
|
|
|
### Ampliación: plegado visual del editor
|
|
|
|
- `QuoteChaptersDataTable.tsx`: botón accesible con `aria-expanded` y etiquetas traducidas en `es/en/ca/fr`. Estado local por capítulo, vinculado a su identificador; no forma parte del formulario ni de los DTO. Conserva el título, subtotal y acciones del capítulo visibles.
|
|
- `QuoteItemsSortableTableRow.tsx`: soporte para filas ocultas. Los campos permanecen montados para conservar valores y validación; los detalles y destinos vacíos plegados se excluyen del movimiento de filas. Los capítulos completos siguen pudiendo desplazarse.
|
|
- `QuoteChapterEditor.test.tsx`: plegar/desplegar sin alterar datos ni payload, conservar campos editados y subtotal, ocultar acciones de fila, mantener plegado al reordenar y desplegar copias de forma independiente.
|
|
- Validación: 22 pruebas de editor y operaciones de capítulos, cinco comprobaciones de traducciones, TypeScript sin emisión y ESLint acotado (dos advertencias previas de contexto/`any` en la fila reutilizable). La suite de editor emite un aviso de actualización asíncrona de paginación fuera de `act`; las dos pruebas nuevas de plegado se ejecutan también aisladas y pasan sin ese aviso. No se modifican ni repiten las pruebas de servidor/PDF, ni se ejecutan E2E o builds de distribución: el cambio se limita al estado visual del cliente.
|
|
|
|
### Ampliación: agrupación visual de capítulos
|
|
|
|
- `QuoteChaptersDataTable.tsx`: estilos mediante clases TailwindCSS inline, utilizando exclusivamente los colores existentes y grises `zinc`. No se añade CSS externo ni se introducen tonos azul/verde. Se mantiene la paleta de los controles actuales.
|
|
- Columna estructural `chapter_tree` de 44 px, exclusiva del editor con capítulos. En la cabecera contiene el control de plegado; en detalles, acciones y subtotal queda vacía para mantener la alineación, sin guías ni bordes verticales. Esta columna no forma parte del formulario, DTO ni PDF.
|
|
- Numeración destacada, icono de carpeta, cabecera gris y subtotal diferenciado, con separadores horizontales y sin marco lateral. Separación de 20 px entre capítulos y sangría de las descripciones. La distinción de capítulos se apoya en límites, espacio y numeración, sin colores alternos. Las cantidades e importes conservan sus tamaños y alineación.
|
|
- Ajustados todos los `colspan`: cabeceras, detalles, acciones de fila, capítulos vacíos y pies. Los campos permanecen montados al plegar; no cambian selección, traslado, duplicado ni cálculos.
|
|
- Dos comprobaciones nuevas verifican que las filas cubran las mismas columnas al mostrar acciones, plegar o tener capítulos vacíos, con descuentos visibles y ocultos; también verifican que no cambie el payload. No se añaden pruebas que reproduzcan el CSS.
|
|
- Validación: 24 pruebas entre editor y operaciones de capítulos; TypeScript sin emisión y ESLint del componente/tests sin errores ni advertencias. Tailwind compila las clases del componente hacia `/tmp/uecko-chapters-neutral.css`, sin modificar `dist` ni instalar dependencias. Persiste el aviso previo de React/TanStack sobre una actualización de paginación fuera de `act` en la suite de editor.
|
|
- Inspección con Chromium de muestra DOM del harness en ambos estados, verificando guía, separación, fondos neutros y subtotal visible. Los campos y sensores de la muestra son dobles de test, por lo que no es un E2E contra la API autenticada. El fixture temporal se retiró; las muestras permanecen en `/tmp/uecko-chapters-neutral-*`.
|
|
- No se ejecutan servidor, base de datos, pruebas PDF ni builds de distribución para este cambio visual. Se conservan los cambios previos de integración MariaDB sin modificación funcional.
|
|
|
|
### Ampliación: orden de columnas del editor
|
|
|
|
- Orden con capítulos: despliegue/plegado, arrastre, selección y resto de columnas existentes. El control de despliegue se encuentra únicamente en la cabecera del capítulo; los detalles reservan esa columna vacía, sin líneas verticales.
|
|
- Orden sin capítulos: arrastre, selección y resto de columnas; no se añade la columna de despliegue.
|
|
- `useDetailColumns.tsx` establece arrastre antes de selección y `QuoteChaptersDataTable.tsx` alinea las cabeceras de capítulo con ese orden. El control de la primera columna muestra `FolderIcon` al estar plegado y `FolderOpenIcon` al estar desplegado; se elimina la carpeta decorativa del título. Se mantienen las clases Tailwind inline existentes.
|
|
- Dos casos de prueba comprueban posición de controles en ambos modos, cabecera de selección, alineación de descripción/cantidad y ausencia de la columna de despliegue en cotizaciones libres. El doble de cabecera del formulario ahora renderiza su contenido, para verificar también la tabla sin capítulos.
|
|
- Validación: 19 pruebas de editor y asa de arrastre, TypeScript sin emisión, ESLint acotado y `git diff --check` correctos. Persiste el aviso previo de paginación React/TanStack fuera de `act`. No se repiten PDF, base de datos ni E2E, al limitarse el cambio al orden de columnas del editor.
|
|
|
|
### Ajuste visual: sin líneas verticales
|
|
|
|
- Se retiran las guías de ramificación, el borde lateral izquierdo, el borde derecho y la franja vertical de fondo. Se conserva vacía la columna de despliegue en las filas de detalle para respetar el orden y alineación de controles.
|
|
- Los capítulos se distinguen por separación de bloques, numeración, fondos grises de título/subtotal y separadores horizontales. Se mantienen los iconos de carpeta abierta/cerrada.
|
|
- Las muestras visuales de `/tmp/uecko-chapters-neutral-*` corresponden a la revisión anterior, con guías; no representan este ajuste. La validación de este cambio se limita a las seis pruebas existentes de plegado, orden y alineación, ESLint acotado y `git diff --check`; no se repiten E2E/PDF ni se modifica persistencia por tratarse de una retirada de decoración.
|
|
|
|
### Ampliación: barra de acciones con grupos de botones
|
|
|
|
- `QuoteItemsSortableDataTableToolbar.tsx` reutiliza `ButtonGroup` existente (importación directa) y los `Button` de la UI. Agrupa altas de fila/artículo/bloque o acciones de selección en un bloque compacto; las acciones de capítulos forman otro grupo. Botones `ghost` pequeños, marcos neutros y clases Tailwind inline; no se instalan componentes ni dependencias.
|
|
- Unifica la estructura de la barra con y sin selección. Se conservan tooltips, textos accesibles, contador seleccionado y los callbacks de tabla o de selección de capítulos. Eliminar mantiene el color destructivo existente y sus confirmaciones siguen en los flujos actuales.
|
|
- La nueva prop opcional `viewControls` coloca el interruptor de importes PDF junto al de descuentos, fuera de los grupos de comandos. `QuoteChaptersDataTable.tsx` y `QuoteDetailsCardEditor.tsx` adaptan los botones de capítulos para mantener una presentación uniforme, también antes de crear el primer capítulo.
|
|
- Disposición con salto de línea: grupos de botones y controles se adaptan al ancho. Chromium verifica las muestras de alta y selección a 1440 y 375 px sin desbordamiento de la barra, con ambos interruptores visibles. HTML generado desde el harness del editor; esta revisión no constituye un E2E con la API autenticada. Artefactos temporales en `/tmp/uecko-toolbar-*`; el fixture temporal se retiró.
|
|
- Dos pruebas nuevas verifican delegación de duplicar/eliminar/deseleccionar en modo líneas y capítulos, y conservación de opciones PDF/descuento fuera de los grupos de botones. Se mantienen las pruebas de confirmación al ocultar descuentos.
|
|
- Validación: 22 pruebas de barra y editor; TypeScript sin emisión; compilación Tailwind hacia `/tmp`; ESLint acotado sin errores, con cuatro advertencias `any` previas en los componentes de barra/editor; `git diff --check` correcto. Permanece el aviso previo de paginación React/TanStack fuera de `act` en la suite de editor.
|
|
- No se ejecutan base de datos, PDF, builds de distribución ni E2E contra la aplicación para este cambio de presentación. No se alteran los datos ni los cálculos de la cotización.
|
|
|
|
|
|
### Ampliación: cabeceras por capítulo, estado vacío y numeración en carpeta
|
|
|
|
- `QuoteChaptersDataTable.tsx` muestra las cabeceras de columnas después del título de cada capítulo con contenido, dentro de su grupo de filas. Se ocultan al plegarlo. Un `colgroup` común mantiene la alineación; las cotizaciones sin capítulos conservan su cabecera global.
|
|
- La casilla de la cabecera selecciona o deselecciona únicamente los detalles de ese capítulo. La casilla del título sigue seleccionando el capítulo completo.
|
|
- Los capítulos vacíos muestran `Empty` con mensaje, icono y acciones para añadir fila vacía, artículo o bloque. Se añade `ui/empty.tsx` siguiendo la composición de [Empty de shadcn/ui](https://ui.shadcn.com/docs/components/empty), adaptada a React 18 y Tailwind 3 sin dependencias nuevas. Los textos están traducidos en `es/en/ca/fr`.
|
|
- Las acciones usan el identificador explícito del capítulo vacío. `QuoteDetailsCardEditor.tsx` y `useChapterEditor.ts` conservan ese destino al abrir un selector, aunque cambie el foco antes de elegir contenido. Al añadir el primer detalle, el estado vacío se sustituye por las cabeceras y la fila.
|
|
- El número de orden sale del título y aparece como `Badge` gris superpuesto al icono de carpeta de la primera columna. Se mantiene al plegar y se actualiza al reordenar; no altera la descripción ni el PDF. Las etiquetas accesibles del botón siguen incluyendo el número.
|
|
- Validación: 28 pruebas de editor/barra y cinco de traducciones; TypeScript sin emisión; ESLint acotado sin errores (tres advertencias `any` previas del editor); Tailwind compilado en `/tmp`; `git diff --check`. Persisten los avisos previos de paginación fuera de `act` y de Browserslist desactualizado.
|
|
- Revisión local con Chromium a 1440 y 375 px sobre HTML del harness: badge sobre carpeta, una cabecera para el capítulo con contenido y tres acciones en el vacío. En móvil la tabla mantiene su desplazamiento horizontal. Artefactos temporales `/tmp/uecko-chapter-empty-*`; el fixture temporal se retiró. No es una prueba E2E con API autenticada.
|
|
- No se ejecutan base de datos, PDF ni builds de distribución: esta ampliación afecta a la presentación e inserción de detalles del editor, sin cambios en persistencia o informes.
|
|
|
|
### Ajuste: alineación del arrastre y plegado animado
|
|
|
|
- `QuoteChaptersDataTable.tsx`: el botón de arrastre del título usa el mismo ancho de 16 px que el de las líneas. Ambos iconos comparten eje horizontal, conservando el orden de columnas y el badge de la carpeta.
|
|
- `useChapterCollapse.ts`: transición de altura y opacidad de 200 ms mediante Web Animations API. Se animan el contenido y el espaciado de las celdas antes de ocultar sus filas; incluye cabeceras, detalles, acciones y estado vacío. Los campos permanecen montados; título y subtotal siguen visibles. Se respeta `prefers-reduced-motion` y se pliega inmediatamente cuando no hay soporte para animación.
|
|
- Durante el cierre, las filas dejan de ser interactivas mediante `inert` y `aria-hidden`; el arrastre de detalles/destinos vacíos se desactiva desde el inicio. Al invertir el sentido, se cancela la transición anterior y se parte de la posición actual. Al desmontar, se cancelan las animaciones pendientes.
|
|
- Dos pruebas nuevas del editor verifican ocultación tras finalizar, reversión sin ocultaciones obsoletas, conservación de valores y movimiento reducido. Validación: 26 pruebas del editor, TypeScript sin emisión, ESLint acotado sin errores y `git diff --check`. Persiste el aviso previo de paginación React/TanStack fuera de `act` en la suite completa; las nuevas pruebas aisladas pasan sin ese aviso.
|
|
- Chromium local verifica geometría sobre el HTML actual del harness y ejecuta el hook real: centros de ambos iconos en 117,53125 px; altura del grupo de 222 a 117 px con estados intermedios de 170 y 184,6875 px; recuperación de altura/valores originales y ausencia de animación con movimiento reducido. Artefactos en `/tmp/uecko-chapter-animation-*`; fixture temporal retirado. Esta comprobación no es un E2E con la API autenticada.
|
|
- No se ejecutan builds de distribución, PDF ni base de datos: son cambios de presentación del editor, sin cambios de persistencia o informes. La compilación Tailwind se dirige a `/tmp` y conserva el aviso previo de Browserslist desactualizado.
|
|
|
|
### Corrección: resaltado conjunto de detalle activo y acciones
|
|
|
|
- `QuoteItemsSortableTableRow.tsx` identifica la fila activa mediante `data-active-item`. `QuoteChaptersDataTable.tsx` aplica el contorno a sus celdas, porque los bordes de las filas no se dibujan en la tabla con `border-separate`.
|
|
- Detalle y acciones comparten fondo rojo claro, borde izquierdo rojo de 4 px y contorno exterior fino; se retira el separador intermedio. Las acciones ocupan todo el ancho de la tabla y empiezan junto al borde izquierdo, como en la presentación original. El estilo de selección no se aplica a los capítulos ni a otras líneas.
|
|
- Validación: 26 pruebas existentes del editor, TypeScript sin emisión, ESLint acotado sin errores (dos advertencias previas en el componente de fila) y `git diff --check`. Chromium local verifica igualdad de fondos, continuidad del contorno y ausencia de espacio o borde entre detalle y acciones. Muestra en `/tmp/uecko-active-row.png`, generada desde el harness del editor; fixture temporal retirado.
|
|
- No se ejecutan base de datos, PDF, builds de distribución ni E2E autenticados: es una corrección de estilos. Persisten los avisos previos de paginación fuera de `act` y Browserslist desactualizado.
|
|
|
|
### Ajuste: selección exclusiva de líneas de detalle
|
|
|
|
- Se elimina el checkbox de los títulos de capítulo, manteniendo su celda vacía para alinear las columnas. La selección se limita a los detalles; el checkbox de cabecera de columnas conserva la selección conjunta de los detalles de ese capítulo.
|
|
- Se retira del editor el estado de selección múltiple de capítulos y su bloqueo por selección mixta. Los capítulos se mueven individualmente con todo su contenido y conservan sus botones propios de duplicar/eliminar. La barra actúa sobre los detalles seleccionados.
|
|
- Se adaptan las pruebas existentes de duplicación, selección y alineación: 26 pruebas del editor correctas, TypeScript sin emisión, ESLint acotado sin errores y `git diff --check`. Persiste el aviso previo de paginación fuera de `act`. No se ejecutan PDF, base de datos ni E2E: el cambio se limita a la selección en el editor.
|
|
|
|
### Ajuste: controles visibles al pasar el cursor
|
|
|
|
- Las filas de detalle muestran arrastre y selección al pasar el cursor por su propia fila, con y sin capítulos. El checkbox marcado permanece visible aunque el cursor salga; al desmarcarlo vuelve a ocultarse. El asa también permanece visible durante el arrastre.
|
|
- En el título de capítulo se muestran arrastre, duplicar y eliminar únicamente al pasar por ese título; el hover de los detalles no activa esos botones. Carpeta y badge siguen visibles. Se usan grupos Tailwind con nombre por tipo de fila y opacidad para mantener la alineación.
|
|
- Cada control se revela también al recibir foco de teclado. En dispositivos sin hover permanecen visibles para permitir su uso táctil. La transición de opacidad respeta movimiento reducido.
|
|
- Cambios en `QuoteChaptersDataTable.tsx`, `QuoteItemsSortableTableRow.tsx`, `QuoteItemsSortableDragHandleCell.tsx` y `useDetailColumns.tsx`. Validación: 27 pruebas existentes de editor/arrastre; ESLint acotado sin errores (dos advertencias previas de la fila); Tailwind compilado en `/tmp`; `git diff --check`. La última excepción de checkbox marcado se comprueba además con CSS real en Chromium.
|
|
- Chromium local verifica ocultación, hover por fila, checkbox marcado/desmarcado, foco de teclado y controles táctiles, con y sin capítulos. Para comprobar selectores de escritorio se desactiva solo la regla táctil en el fixture, ya que Chromium sin interfaz informa `hover:none`; la prueba táctil usa la hoja original. Artefactos `/tmp/uecko-row-controls-*`; fixture temporal retirado. No es un E2E autenticado.
|
|
- No se ejecutan TypeScript, builds de distribución, PDF ni base de datos: el cambio solo añade clases y no altera tipos, datos o informes. Persisten los avisos previos de paginación fuera de `act` y Browserslist desactualizado.
|
|
|
|
### Ajuste: márgenes simétricos de carpeta y eliminar capítulo
|
|
|
|
- `QuoteChaptersDataTable.tsx`: aumenta el espacio izquierdo de la carpeta y amplía la columna de despliegue de 44 a 56 px para alojarlo sin invadir la columna de arrastre. Conserva tamaños de iconos y botones, badge y alineación de detalles.
|
|
- Chromium local mide 20 px tanto entre el borde izquierdo y la carpeta como entre la papelera y el borde derecho, en ambos capítulos a 1440 y 1024 px. El eje de las asas de título/detalle sigue coincidiendo (diferencia 0 px). HTML exportado del harness en `/tmp/uecko-chapter-spacing.html`; fixture temporal retirado.
|
|
- Validación acotada: cuatro casos existentes de alineación/orden de columnas, ESLint del componente, compilación Tailwind hacia `/tmp` y `git diff --check`. No se ejecutan TypeScript, PDF, base de datos, builds de distribución ni E2E autenticados por ser un ajuste de espaciado. Persiste el aviso previo de Browserslist desactualizado.
|
|
|
|
### Ajuste: título alineado con el arrastre de sus conceptos
|
|
|
|
- El título de capítulo ocupa las columnas posteriores a la carpeta y empieza en el mismo eje vertical que el icono de arrastre de sus detalles. El asa del capítulo se sitúa a la derecha del título, junto a duplicar y eliminar; conserva eventos de arrastre, foco y visibilidad al pasar el cursor.
|
|
- `QuoteChaptersDataTable.tsx` ajusta el colspan y espaciado del título. `QuoteChapterEditor.test.tsx` adapta las comprobaciones de estructura y badge a las dos celdas del título. Se mantienen orden de columnas de los detalles y márgenes exteriores de 20 px.
|
|
- Validación: cinco casos existentes de alineación, orden y numeración (ejecutados también en el fixture temporal), ESLint sin errores, compilación Tailwind en `/tmp` y `git diff --check`. Chromium local mide diferencia de 0 px entre inicio del texto del título y lado izquierdo del icono de concepto, a 1440 y 1024 px, manteniendo márgenes de carpeta/papelera de 20 px. Fixture temporal retirado; muestra `/tmp/uecko-chapter-title.png`.
|
|
- No se ejecutan TypeScript, PDF, base de datos, build de distribución ni E2E autenticado: es un cambio local de disposición, sin cambios de tipos o datos. Persiste el aviso previo de Browserslist desactualizado.
|
|
|
|
### Ajuste: carpeta como asa de arrastre del capítulo
|
|
|
|
- `QuoteChaptersDataTable.tsx` vincula `setActivatorNodeRef`, atributos y listeners de `useSortable` al botón Folder/FolderOpen. Se elimina el asa independiente de la derecha. La carpeta conserva badge, márgenes, clic de plegado y estado accesible `aria-expanded`.
|
|
- El puntero muestra `grab`/`grabbing` y una ayuda nativa (`title`) explica que arrastra todo el capítulo y que el clic pliega/despliega. La misma ayuda se expone con `aria-description` y se traduce en `es/en/ca/fr`.
|
|
- Con teclado, Espacio inicia el arrastre y las flechas eligen destino; Entrar confirma el movimiento. Fuera de un arrastre, Entrar conserva el clic nativo de plegado. El click no cambia el plegado mientras se arrastra; el sensor real suprime el clic tras completar un movimiento con puntero.
|
|
- `QuoteChapterEditor.test.tsx` adapta las comprobaciones anteriores y verifica activador/ref, listeners de puntero, separación de Enter/Espacio, ayuda y conservación de datos al plegar. Validación: 27 pruebas de editor y cinco comprobaciones de traducciones; TypeScript sin emisión; ESLint acotado sin errores; `git diff --check`.
|
|
- Chromium ejecuta el componente, hook y sensores reales desde un bundle temporal en `/tmp`, con formulario local y sin API. Comprueba clic, reordenación mediante carpeta de capítulos abiertos/cerrados conservando contenido y plegado, y movimiento/plegado con teclado. Artefactos `/tmp/uecko-folder-drag-*` y `/tmp/uecko-folder-drag.png`; no se añaden dependencias ni fixtures al repositorio.
|
|
- No se ejecutan PDF, base de datos, build de distribución ni E2E autenticado: cambia únicamente la interacción del editor. Persisten los avisos previos de paginación fuera de `act` y Browserslist desactualizado.
|
|
|
|
### Ajuste: eliminar columna vacía a la izquierda de los conceptos
|
|
|
|
- Se elimina la columna estructural `chapter_tree` y sus celdas vacías de los detalles, cabeceras, subtotales y capítulos vacíos. Las líneas empiezan directamente por arrastre y selección, igual que en una cotización sin capítulos.
|
|
- El título usa una única celda que abarca todas las columnas; carpeta, descripción y acciones se disponen dentro de ella con Tailwind inline. Conserva posición de la carpeta, badge, márgenes exteriores de 20 px y sus funciones de arrastre/plegado, sin reservar espacio en las filas inferiores.
|
|
- `QuoteChaptersDataTable.tsx` utiliza directamente las columnas de detalle y ajusta los colspan. `QuoteChapterEditor.test.tsx` adapta las comprobaciones de alineación, orden y badge a la nueva estructura. Validación: 27 pruebas de editor, TypeScript sin emisión, ESLint sin errores, Tailwind hacia `/tmp` y `git diff --check`.
|
|
- Chromium sobre HTML del harness confirma ausencia de columnas extra, arrastre a 8 px del inicio de la fila, checkbox en segunda columna y coincidencia de columnas/span de título, cabecera, detalles, subtotal y estado vacío, a 1440 y 1024 px. Carpeta/papelera mantienen márgenes de 20 px. Muestra `/tmp/uecko-chapter-gutter.png`; fixture temporal retirado.
|
|
- No se ejecutan PDF, base de datos, build de distribución ni E2E autenticado por ser un cambio de disposición del editor. Persisten los avisos previos de paginación fuera de `act` y Browserslist desactualizado.
|
|
|
|
### Ajuste: contorno gris del capítulo completo
|
|
|
|
- `QuoteChaptersDataTable.tsx` añade bordes laterales grises de 1 px a las celdas exteriores de título, cabeceras, conceptos, estado vacío y subtotal. Junto con los bordes superior/inferior existentes forman un único contorno por capítulo, sin líneas verticales entre columnas ni bordes en la separación entre capítulos.
|
|
- Se conserva el resaltado rojo del conjunto detalle/acciones activo. El contorno sigue visible en capítulos vacíos y plegados; no se añaden columnas ni se cambian interacciones.
|
|
- Validación: cuatro casos existentes de alineación/orden y un fixture visual temporal; ESLint sin errores; Tailwind compilado en `/tmp`; `git diff --check`. Chromium verifica bordes exteriores de 1 px grises, ausencia de bordes verticales interiores y separación sin contorno, en estado normal, activo y plegado. Muestras `/tmp/uecko-chapter-border*.png`; fixture temporal retirado.
|
|
- No se ejecutan TypeScript, PDF, base de datos, build de distribución ni E2E autenticado: el cambio añade únicamente clases de estilo. Persiste el aviso previo de Browserslist desactualizado.
|
|
|
|
|
|
### Ajuste: etiqueta breve de importes PDF
|
|
|
|
- Se sustituye la etiqueta por «Importes por línea en PDF» y se actualizan sus traducciones en `es/en/ca/fr`. Activado sigue mostrando los importes de cada detalle en el PDF; desactivado mantiene únicamente los subtotales de capítulo. No se invierte ni cambia la preferencia guardada.
|
|
- Se adapta la prueba de preferencia del editor. Validación acotada: prueba de persistencia PDF y cinco comprobaciones de traducciones; `git diff --check`. No se ejecutan builds, TypeScript, base de datos, generación de PDF ni E2E por ser un cambio de texto.
|
|
|
|
### Ajuste: diálogo de eliminación con alternativas separadas
|
|
|
|
- `QuoteChaptersDataTable.tsx` distingue eliminar un capítulo de eliminar únicamente conceptos seleccionados. El diálogo de capítulo presenta dos secciones con nombre accesible, explicación propia y botón independiente: «Conservar conceptos» con traslado al destino elegido y «Eliminar capítulo y conceptos» con tratamiento destructivo.
|
|
- El selector de destino pertenece únicamente al bloque de conservación; el botón «Trasladar conceptos y quitar capítulo» permanece deshabilitado hasta elegir destino. «Cancelar» está separado en el pie. Si se elimina el último capítulo, la alternativa de conservación explica que los conceptos quedarán sin agrupar y conserva sus importes.
|
|
- El diálogo tiene ancho adaptable, botones con texto ajustable y desplazamiento vertical si falta altura. Se conservan la lógica de eliminación/traslado y la eliminación inmediata de capítulos vacíos. Nuevos textos en `es/en/ca/fr`; las cantidades se presentan como «Conceptos afectados: N» para evitar concordancias incorrectas con uno.
|
|
- `QuoteChapterEditor.test.tsx` verifica separación de acciones, destino obligatorio, transferencia conservando valores y eliminación exclusiva de conceptos sin borrar sus capítulos; se adaptan los textos de las confirmaciones existentes. Validación: 10 pruebas acotadas (cinco del editor y cinco de traducciones), TypeScript sin emisión, ESLint sin errores, Tailwind hacia `/tmp` y `git diff --check`.
|
|
- Chromium ejecuta componente, formulario y selectores reales en un bundle local sin API a 1440 y 375 px. Verifica separación de bloques, ausencia de desbordamiento horizontal, desactivación sin destino, traslado preservando conceptos/precios y conservación sin agrupar al quitar el último capítulo. Muestras `/tmp/uecko-delete-chapter-*.png`; no se incorporan fixtures ni dependencias al repositorio.
|
|
- No se ejecutan PDF, base de datos, build de distribución ni E2E autenticado: cambia la presentación de decisiones existentes en el editor. Persisten los avisos previos de paginación fuera de `act` y Browserslist desactualizado.
|
|
|
|
### Ampliación: estado vacío para cotizaciones sin líneas ni capítulos
|
|
|
|
- `QuoteItemsSortableDataTable.tsx` muestra `Empty` cuando no hay detalles, en lugar de cabeceras y tabla sin contenido. Mensaje, icono neutro y botones para añadir fila vacía, artículo del catálogo o bloque de texto; usa las mismas acciones de tabla que la barra existente. El pie de botones se oculta mientras está vacío para no duplicar las acciones del propio `Empty`.
|
|
- Al insertar el primer concepto aparece la tabla normal, sin crear capítulos. El estado también se aplica cuando una cotización vuelve a quedarse sin conceptos. Las cotizaciones con capítulos conservan sus estados vacíos propios. Nuevos textos `quotes.empty_details` en `es/en/ca/fr`.
|
|
- `QuoteChapterEditor.test.tsx` incorpora un formulario inicialmente vacío y tres pruebas de alta desde `Empty`, incluyendo los dos selectores y conservación del modo sin capítulos. Validación: tres pruebas nuevas y cinco de traducciones, tres casos existentes de tabla libre/orden de columnas, TypeScript sin emisión, ESLint acotado sin errores (cuatro avisos `any` previos), Tailwind hacia `/tmp` y `git diff --check`.
|
|
- Chromium sobre el HTML del harness revisa a 1440 y 375 px la ausencia de tabla vacía, los tres botones, ajuste al ancho y ausencia de desbordamiento. Muestras `/tmp/uecko-new-empty-*.png`; fixture temporal retirado. No se ejecutan PDF, base de datos, build de distribución ni E2E autenticado al limitarse al editor.
|
|
|
|
|
|
### Refactorización: tabla de conceptos con componentes reutilizables
|
|
|
|
- `QuoteItemsSortableDataTable.tsx` pasa de 593 a 88 líneas y queda como composición de la tarjeta, barra, tabla, estado vacío, pie y overlay. Mantiene su API y reexporta los tipos anteriores para conservar las importaciones existentes.
|
|
- `QuoteItemsAddActions.tsx` centraliza los botones de fila/artículo/bloque mediante callbacks, con tamaño configurable. `QuoteItemsEmptyState.tsx` compone `Empty` con título, descripción, icono y acciones. Ambos se reutilizan en la tabla sin capítulos y en los capítulos vacíos; estos conservan su destino droppable y animación propios.
|
|
- `QuoteItemsTableHeader.tsx`, `QuoteItemsSortableTableBody.tsx` y `QuoteItemsDragOverlay.tsx` separan cabecera genérica TanStack, filas/acciones activas y representación del arrastre. Se reutilizan las primitivas existentes de tabla, cabecera y filas; se elimina la vista previa de arrastre desactivada por `false &&`, que nunca se renderizaba.
|
|
- `useQuoteItemsSortableTable.ts` concentra selección, metadatos de edición, sensores y manejadores de arrastre, conservando sus operaciones y orden. `QuoteItemsSortableDataTable.types.ts` recoge los contratos compartidos y la ampliación de `TableMeta`. Los imports internos de tipos dejan de depender del componente principal; se mantienen los barrels del feature.
|
|
- Cuatro pruebas nuevas del hook con React Hook Form real verifican duplicación de valores editados, eliminación selectiva, movimiento conjunto de filas seleccionadas y cancelación/destino exterior sin cambios. Se corrige una expectativa obsoleta de la prueba de barra, repetida para dos modos: comprueba sus tres acciones agrupadas sin exigir un número fijo de grupos del diseño anterior. No se modifica la barra de producción.
|
|
- Validación: 40 pruebas de hook/editor/barra/asa correctas, TypeScript sin emisión, ESLint acotado sin errores (dos `any` heredados de los contratos, más dos avisos anteriores en la fila reutilizable), compilación Tailwind hacia `/tmp` y `git diff --check`. Persiste el aviso anterior de paginación React/TanStack fuera de `act` en la suite del editor.
|
|
- Chromium compara bundles locales de la versión anterior y la refactorizada: capturas idénticas byte a byte para estado vacío a 1440/375 px, filas normales, fila activa y selección a 1440 px. Comprueba también que añadir desde el estado vacío siga mostrando la tabla. Artefactos `/tmp/uecko-sortable-refactor-*`; no se añaden dependencias ni fixtures visuales al repositorio.
|
|
- No se ejecutan PDF, base de datos, build de distribución ni E2E autenticado: la refactorización afecta al cliente y conserva los flujos existentes. Las pruebas de navegador usan formulario y componentes reales en un harness local sin API. Persiste el aviso previo de Browserslist desactualizado.
|