Skip to content

merge: el PDF nativo con FPDF llega a feat/app-documentate - #283

Merged
erseco merged 3 commits into
feat/app-documentatefrom
integra/app-documentate-pdf-nativo
Sep 8, 2026
Merged

merge: el PDF nativo con FPDF llega a feat/app-documentate#283
erseco merged 3 commits into
feat/app-documentatefrom
integra/app-documentate-pdf-nativo

Conversation

@erseco

@erseco erseco commented Sep 6, 2026

Copy link
Copy Markdown
Member

Qué es

Traer a feat/app-documentate lo que se mezcló en main con el PR #282: el PDF se dibuja en el servidor con FPDF a partir de maquetas HTML por tipo de documento, Collabora Online y el conversor WASM quedan como motores seleccionables, la descarga editable se ofrece solo en el formato de la plantilla, y los ficheros generados quedan protegidos en uploads/documentate/.

Es una mezcla de main, no un rehacer: un solo commit de merge.

Adaptado a las convenciones de esta rama

Sin i18n. Las doce cadenas que llegaban con __() y esc_html_e() pasan a literales en castellano, y languages/ sigue sin existir. Los tests que afirmaban sobre el texto en inglés afirman ahora sobre el castellano.

En main Aquí
Native PDF rendering PDF nativo (FPDF)
Editable download: Descarga editable:
PDF layout Maqueta PDF
The generated PDF could not be saved. No se ha podido guardar el PDF generado.

Conflictos y cómo se resolvieron

Once ficheros, resueltos a favor de la estructura de main y del idioma de esta rama.

  • Ajustes y tipos de documento: entra el motor fpdf y el selector de maqueta PDF, con sus etiquetas en castellano.
  • Documentate_Admin_Helper: build_format_state() cede ante own_format_state(), que ofrece solo el formato de la plantilla del tipo. Los tests de la función que desaparece se eliminan con ella.
  • Datos de ejemplo: las definiciones conservan prefix y has_management de esta rama junto a pdf_layout de main.
  • protect_output_dir() se elimina: Documentate_Private_Output hace lo mismo y más — permisos, migración de ficheros anteriores y fallo cerrado. El .htaccess que dejara la versión anterior sigue siendo válido, porque ya declara «Require all denied».
  • Spec de previsualización: se queda la versión de main, que parchea window.open en vez de esperar a que la pestaña pinte; el endpoint que abre el botón es documentate_preview_stream, que es sobre el que afirma.
  • composer.lock: regenerado con composer update --lock sobre el composer.json ya fusionado, que aquí no lleva wp-cli/i18n-command.

E2E adaptado

Los specs que llegan de main daban por hecho que un documento publicado se puede editar. Aquí el flujo lo aprueba y lo bloquea, y el metabox de acciones solo se pinta para quien puede editar el documento, así que los cuatro tests que afirman sobre sus botones lo devuelven antes a revisión. El botón se llama «Previsualizar PDF» en esta rama, no «Vista previa».

El bloque de exportación de la aplicación esperaba un botón DOCX deshabilitado en un tipo con plantilla ODT. Con la descarga editable limitada al formato propio, ese control ya no existe apagado: no existe. El spec lo comprueba ahora así.

Tres specs de la aplicación (documentate-app.spec.js, documentate-app-flow.spec.js, documentate-app-roles.spec.js) fallaban por datos acumulados en el sitio de desarrollo, no por la mezcla: fallan igual en feat/app-documentate sin ella, y pasan tras make reset-demo.

Presupuesto de PHPMD

La mezcla cruzaba la puerta por cinco líneas. Sin tocar phpmd-baseline.xml:

  • Documentate_Demo_Data estaba en 2504 líneas. La tabla de valores de ejemplo, 155 líneas de datos con un solo punto de llamada, se va a Documentate_Demo_Field_Values.
  • load_dependencies() estaba en 151. La carga de la aplicación de /documentate/ pasa a su propio método.

Verificación

Comprobación Resultado
make test 2775 tests, 17 782 aserciones
make test-generation 412 tests, 9981 aserciones
make lint limpio
make phpmd sin violaciones fuera del baseline
make check-plugin sin errores
make test-e2e 105 pasan, 4 omitidos, 0 fallan (tras make reset-demo)

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

WordPress Playground Preview

The changes in this pull request can previewed and tested using a WordPress Playground instance.

Open WordPress Playground Preview

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

erseco added a commit that referenced this pull request Sep 6, 2026
…#283

El PDF nativo se dibujaba dentro de un `<div>` propio, así que un `</div>`
sobrante en un campo rich —que `wp_kses_post()` conserva y que un layout con
`strconv=no` inyecta tal cual— cerraba el envoltorio y tiraba en silencio todo
lo que venía detrás: el RESUELVO, la firma y los anexos. Se dibuja desde un
`<body>`, donde libxml se recupera de la etiqueta sobrante en vez de obedecerla.

Las filas de un repetidor se emparejaban con las guardadas por posición, y el
navegador renumera las filas en cada alta, baja y arrastre: el área podía mover
el IGIC o el importe aprobado de gestión a otro proveedor sin tocar la columna.
Ahora cada fila lleva su identidad en un `__row` oculto.

El resto:

- Un adjunto se servía inline con su propio mime, y la metacaja de wp-admin
  escribe esa misma meta desde cualquier ID de la mediateca: un SVG o un HTML
  se ejecutaba en el origen del sitio. Lo que no aceptó la aplicación se
  entrega como descarga opaca.
- El filtro `comments_clauses` se registraba por consulta y se desenganchaba
  solo en su primera llamada, que no ocurre cuando `WP_Comment_Query` responde
  desde su caché: quedaba puesto y vaciaba la actividad de un documento que su
  lector sí puede ver. Se registra una vez y decide mirando la consulta.
- `remove_role()` dejaba a los miembros del rol sin capacidad ninguna, ni
  `read`. Ahora pasan al rol por defecto antes de quitarlo.
- `page_url()` resolvía por slug e ignoraba `documentate_app_page_id`: renombrar
  la página dejaba en blanco todos los enlaces y redirecciones.
- `devolver_area` existe desde `en_gestion` y desde `pending`, y
  `confirmation()`, `redirect()` y `flag()` sólo miraban la clave.
- `mode()` fallaba en cualquier sistema donde `chmod` es cosmético (Windows,
  CIFS, bind mounts), bloqueando toda generación; y un render abortado dejaba
  su `.tmp` para siempre.
- `assign_missing()` escribía el marcador aunque `get_terms()` fallara, con lo
  que un tropiezo cancelaba la migración de layouts para siempre.
- La desinstalación dejaba la página, cuatro opciones y los documentos
  generados, que llevan datos personales.
- El último `__()` del plugin, en inglés sobre una interfaz en español.
- `nombre_completo` nunca llegaba a su valor de ejemplo, y el autoloader del
  PDF se apilaba una vez por instancia.
erseco added a commit that referenced this pull request Sep 6, 2026
…oj no avance

`test_existing_document_preserves_dates` insertaba un borrador sin fecha, leía
su `post_date` y comprobaba que el update no lo movía. Un borrador sin fecha
lleva `post_date_gmt` a ceros, que para WordPress significa «esta fecha aún
flota» y la refresca en cada guardado: la comprobación solo se cumplía si el
insert y el update caían dentro del mismo segundo, y en CI no siempre caen.
Falló así en el último run de #283, esperando 06:50:23 y recibiendo 06:50:24.

Con la fecha puesta a mano deja de flotar, y cualquier desplazamiento pasa a
ser cosa del plugin, que es justo lo que el test quiere vigilar.
@erseco
erseco force-pushed the integra/app-documentate-pdf-nativo branch from daa68fe to 87f11ab Compare September 8, 2026 17:04
@erseco
erseco force-pushed the integra/app-documentate-pdf-nativo branch from 87f11ab to fdbb5c9 Compare September 8, 2026 17:18
El metabox de acciones solo se pinta para quien puede editar el documento,
y aquí un documento publicado queda aprobado y bloqueado: los cuatro tests
que afirman sobre sus botones lo devuelven antes a revisión. El botón se
llama «Previsualizar PDF» en esta rama.

El bloque de exportación ya no ofrece un DOCX deshabilitado para un tipo
con plantilla ODT: la descarga editable solo ofrece el formato propio, así
que el control no existe en vez de existir apagado.
…#283

El PDF nativo se dibujaba dentro de un `<div>` propio, así que un `</div>`
sobrante en un campo rich —que `wp_kses_post()` conserva y que un layout con
`strconv=no` inyecta tal cual— cerraba el envoltorio y tiraba en silencio todo
lo que venía detrás: el RESUELVO, la firma y los anexos. Se dibuja desde un
`<body>`, donde libxml se recupera de la etiqueta sobrante en vez de obedecerla.

Las filas de un repetidor se emparejaban con las guardadas por posición, y el
navegador renumera las filas en cada alta, baja y arrastre: el área podía mover
el IGIC o el importe aprobado de gestión a otro proveedor sin tocar la columna.
Ahora cada fila lleva su identidad en un `__row` oculto.

El resto:

- Un adjunto se servía inline con su propio mime, y la metacaja de wp-admin
  escribe esa misma meta desde cualquier ID de la mediateca: un SVG o un HTML
  se ejecutaba en el origen del sitio. Lo que no aceptó la aplicación se
  entrega como descarga opaca.
- El filtro `comments_clauses` se registraba por consulta y se desenganchaba
  solo en su primera llamada, que no ocurre cuando `WP_Comment_Query` responde
  desde su caché: quedaba puesto y vaciaba la actividad de un documento que su
  lector sí puede ver. Se registra una vez y decide mirando la consulta.
- `remove_role()` dejaba a los miembros del rol sin capacidad ninguna, ni
  `read`. Ahora pasan al rol por defecto antes de quitarlo.
- `page_url()` resolvía por slug e ignoraba `documentate_app_page_id`: renombrar
  la página dejaba en blanco todos los enlaces y redirecciones.
- `devolver_area` existe desde `en_gestion` y desde `pending`, y
  `confirmation()`, `redirect()` y `flag()` sólo miraban la clave.
- `mode()` fallaba en cualquier sistema donde `chmod` es cosmético (Windows,
  CIFS, bind mounts), bloqueando toda generación; y un render abortado dejaba
  su `.tmp` para siempre.
- `assign_missing()` escribía el marcador aunque `get_terms()` fallara, con lo
  que un tropiezo cancelaba la migración de layouts para siempre.
- La desinstalación dejaba la página, cuatro opciones y los documentos
  generados, que llevan datos personales.
- El último `__()` del plugin, en inglés sobre una interfaz en español.
- `nombre_completo` nunca llegaba a su valor de ejemplo, y el autoloader del
  PDF se apilaba una vez por instancia.
…oj no avance

`test_existing_document_preserves_dates` insertaba un borrador sin fecha, leía
su `post_date` y comprobaba que el update no lo movía. Un borrador sin fecha
lleva `post_date_gmt` a ceros, que para WordPress significa «esta fecha aún
flota» y la refresca en cada guardado: la comprobación solo se cumplía si el
insert y el update caían dentro del mismo segundo, y en CI no siempre caen.
Falló así en el último run de #283, esperando 06:50:23 y recibiendo 06:50:24.

Con la fecha puesta a mano deja de flotar, y cualquier desplazamiento pasa a
ser cosa del plugin, que es justo lo que el test quiere vigilar.
@erseco
erseco force-pushed the integra/app-documentate-pdf-nativo branch from fdbb5c9 to 0d66cae Compare September 8, 2026 18:51
@erseco
erseco merged commit 2daa5de into main Sep 8, 2026
14 of 22 checks passed
@erseco
erseco deleted the integra/app-documentate-pdf-nativo branch September 8, 2026 18:55
erseco added a commit that referenced this pull request Sep 8, 2026
* refactor: la limpieza y el rendimiento que quedaron fuera de #283

Apilado sobre #283, que se queda con los fallos de correctitud y seguridad.
Aquí no cambia nada de lo que ve el usuario.

Los dos que la revisión pedía y allí no cabían:

- El motor nativo estaba pegado a `Documentate_Admin_Helper` como cuatro «si es
  nativo, sal por aquí» repartidos por una clase con forma de conversor, y cada
  uno preguntaba el nombre del motor para deducir lo mismo. Quién puede hacer
  qué es asunto de `Documentate_Conversion_Manager`, así que ahora lo dice él
  en `capabilities()` y la metacaja lee la respuesta en vez de volver a
  derivarla. Un quinto motor se añade en un sitio, no en cuatro.
- El layout genérico recorría el esquema dos veces: una para los campos de
  mezcla y otra para las filas que se dibujan con ellos, y la segunda volvía a
  leer el tipo, a parsear el cuerpo entero y a resolver cada campo para llegar
  a la misma respuesta. Ahora las filas salen del mismo recorrido.
  `build_generic_rows()` desaparece: era ese segundo recorrido.

Y el resto de la limpieza:

- `Documentate_Pdf_Layout::slugs()` / `is_shipped()` responden «¿existe este
  layout?» con los nombres de fichero. Antes se abrían y se parseaban los once
  layouts, con `DOMDocument`, para tirar después los títulos — en cada PDF y en
  cada pantalla de tipo de documento.
- `Documentate_Pdf_Table_Writer` construía un `DOMXPath` sobre el documento
  entero en cada consulta, varias por fila y por celda, y dos veces: al medir y
  al dibujar. Ahora se construye uno por documento.
- Pintar una bandeja lanzaba una decena de `WP_Query` de conteo, varias
  idénticas. Se recuerdan durante la petición, con la marca de cambios de
  `posts` en la clave, así que cualquier escritura las invalida sola.
- El stepper repetía la misma consulta de eventos por cada paso completado.
- `own_format_state()` seguía construyendo `available` y `message`, que ya no
  lee nadie desde que se borró la rama del botón deshabilitado.
- Un filtro `wp_insert_post_empty_content` en cada inserción cuyo cuerpo entero
  era `return $maybe_empty;`.
- `.distignore` deja fuera los `.ttf`: FPDF sólo lee el `.json` y el `.z`, y el
  TrueType son 157 KB muertos en cada release. Sigue en git, que es de donde el
  README regenera el par.
- Dos specs E2E: cuatro tests desbloqueaban un documento publicado para ver una
  metacaja que sólo mira `edit_post` —otros cuatro del mismo fichero ya la
  miraban sin desbloquear— y una rama de `checkControl()` que ninguna fila
  podía activar desde que se quitó DOCX.

* test: capabilities() se prueba rama a rama, y en la clase que la declara

El informe de cobertura del PR daba 0 % para
`class-documentate-conversion-manager.php`: el método lo ejercitaba un test de
la metacaja, que declara `@covers Documentate_Admin_Helper`, así que las líneas
que ejecutaba en otra clase no se le atribuían a esa clase.

Las pruebas van ahora a `DocumentateConversionManagerTest`, que es quien
declara `@covers Documentate_Conversion_Manager`, y cubren las cuatro
respuestas que el método puede dar: el motor nativo, Collabora accesible,
Collabora dentro de Playground —donde la conversión la hace un fetch del
navegador y hace falta el documento base— y un motor que no se alcanza y
tampoco tiene ventana. De paso queda escrito que el motor nativo sigue siendo
nativo dentro de Playground, que es lo que desvía la previsualización al visor
incrustado.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant