<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gabriel Bernardo Paredes Abreu</title>
    <description>The latest articles on Forem by Gabriel Bernardo Paredes Abreu (@gabriel_bernardoparedes).</description>
    <link>https://forem.com/gabriel_bernardoparedes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1808306%2F410e5284-7c58-4c12-844e-69114797852c.jpg</url>
      <title>Forem: Gabriel Bernardo Paredes Abreu</title>
      <link>https://forem.com/gabriel_bernardoparedes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gabriel_bernardoparedes"/>
    <language>en</language>
    <item>
      <title>Más allá del CRUD: Cómo arquitecté un Motor Dinámico de Compaginación PDF utilizando Go</title>
      <dc:creator>Gabriel Bernardo Paredes Abreu</dc:creator>
      <pubDate>Fri, 24 Apr 2026 10:04:41 +0000</pubDate>
      <link>https://forem.com/gabriel_bernardoparedes/mas-alla-del-crud-como-arquitecte-un-motor-dinamico-de-compaginacion-pdf-utilizando-go-47da</link>
      <guid>https://forem.com/gabriel_bernardoparedes/mas-alla-del-crud-como-arquitecte-un-motor-dinamico-de-compaginacion-pdf-utilizando-go-47da</guid>
      <description>&lt;h2&gt;
  
  
  Decisiones de diseño, manejo de I/O pesado de binarios y el cálculo de algoritmos para paginaciones predecibles.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Llega un punto en la trayectoria de todo ingeniero backend donde nos preguntamos: ¿Todo en la web se resume a guardar y extraer datos de una base de datos? Afortunadamente, no. Las aplicaciones operativas serias requieren procesos heavy-computation.&lt;br&gt;
Recientemente, desarrollé un Sistema de Gestión Documental corporativo. &lt;br&gt;
El dominio requería que los usuarios estructuraran árboles jerárquicos de información (Documento → Múltiples Secciones → Múltiples Insumos/PDFs preexistentes). La "magia" residía en que el sistema, a nivel de backend, debía calcular el peso, generar índices visuales dinámicos, compaginar físicamente e inyectar numeración a cientos de páginas en un solo stream. Acá expongo sobre cómo Go facilitó el proceso.&lt;/p&gt;
&lt;h2&gt;
  
  
  El Diseño del Sistema: Del Dominio al Binario
&lt;/h2&gt;

&lt;p&gt;Construir una maraña de mergeos secuenciales hubiese reventado el consumo de memoria I/O y agotado el File System. Dividí la carga en tres componentes fundamentales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;La Jerarquía de Dominio:&lt;/strong&gt; Base de datos estructurada que mapea virtualmente el documento vía API usando el clásico trío Handlers-Services-Repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La Planificación (PlanConstruccion):&lt;/strong&gt; Antes de mover un solo bit real, un Pipeline recorre el árbol mapeando en memoria lo que sería el esqueleto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El Motor PDF (Aislado):&lt;/strong&gt; Una caja negra completamente abstracta (pdfcpu y gofpdf) que solo obedece a nuestro esqueleto inmutable, operando bajo los siguientes pasos calculados: &lt;code&gt;Validar -&amp;gt; Limpiar -&amp;gt; Calcular Offsets -&amp;gt; Renderizar Índice Visual -&amp;gt; Merge -&amp;gt; Estampar Numeración.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Decisiones Técnicas e Infraestructura
&lt;/h2&gt;

&lt;p&gt;Elegí fervientemente Go por sus superpoderes concurrentes, su excelente latencia en acceso al disco y su binario ejecutable predecible. En segundo lugar, quité toda referencia de librerías PDF dentro de la capa del Dominio; si el día de mañana deseamos llevar la creación a un Microservicio o Cola de Eventos (Event-Driven), el paquete Motor se migraría sin cambiar una sola línea de lógica comercial.&lt;/p&gt;
&lt;h2&gt;
  
  
  Retos de Algoritmia Encontrados
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;La Paradoja del Índice Visual:&lt;/strong&gt; Para saber exactamente en qué página de fondo arranca el "Capítulo 4", necesitamos saber la numeración de offset. Pero… ¿cómo sé cuánto offset ocupa el Índice si el Índice depende del número de las secciones? Lo resolvimos calculando de antemano el dibujado matemático del canvas en memoria (gofpdf) permitiendo apartar la cantidad exacta de "hojas hipotéticas".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compaginado a Doble Cara (Impresión Física):&lt;/strong&gt; En el formato libro, un bloque denso no puede darse el lujo de iniciar en una página impar izquierda, arruinando la legibilidad física de la institución. Añadí un condicional robusto (MoverPaginaDerecha); que frente a escenarios pares, inyecta buffers limpios en blanco al Merge para obligar el salto natural de carro.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Para mantener el bajo acoplamiento, creamos un planificador algorítmico. Su tarea es barrer la colección inyectando hojas virtuales si el comportamiento comercial así lo requiere, mucho antes de tocar siquiera los módulos pesado de combinación (Merge).&lt;br&gt;
package document_engine&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="c"&gt;// PDFNode representa una estructura simplificada de nuestra jerarquía de negocio&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;PDFNode&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;Name&lt;/span&gt;             &lt;span class="kt"&gt;string&lt;/span&gt;
 &lt;span class="n"&gt;TotalPages&lt;/span&gt;       &lt;span class="kt"&gt;int&lt;/span&gt;
 &lt;span class="n"&gt;RequiresRightAlignment&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;   &lt;span class="c"&gt;// Regla comercial: Si debe imprimirse a la derecha (Página impar)&lt;/span&gt;
 &lt;span class="n"&gt;InjectBlankPage&lt;/span&gt;  &lt;span class="kt"&gt;bool&lt;/span&gt;         &lt;span class="c"&gt;// Bandera de buffer virtual &lt;/span&gt;
 &lt;span class="n"&gt;CalculatedOffset&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;          &lt;span class="c"&gt;// Cuál será su página física de arranque real&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// CalculatePagination offsets procesa el árbol y devuelve el recuento matemático&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CalculatePaginationOffsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;PDFNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;currentPage&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c"&gt;// En la realidad de impresión, siempre arrancamos en 1&lt;/span&gt;

 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Validamos si la regla de negocio física choca con la página actual&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequiresRightAlignment&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;currentPage&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-&amp;gt; [Lógica] Colisión par. Inyectando buffer en blanco previo a '%s'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="c"&gt;// Mutamos el estado y forzamos a una página impar para resolver el choque&lt;/span&gt;
   &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InjectBlankPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
   &lt;span class="n"&gt;currentPage&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Asignamos el offset real de dónde empezará este componente en el reporte master&lt;/span&gt;
  &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalculatedOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentPage&lt;/span&gt;

  &lt;span class="c"&gt;// Sumamos su peso para el siguiente nodo del ciclo&lt;/span&gt;
  &lt;span class="n"&gt;currentPage&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalPages&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="n"&gt;totalAssignedPages&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;currentPage&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalAssignedPages&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Ejemplo de simulación&lt;/span&gt;
 &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;PDFNode&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Portada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TotalPages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequiresRightAlignment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Capitulo_1_Intro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TotalPages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequiresRightAlignment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c"&gt;// Arrancará en 3, salta la 2&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Capitulo_2_Data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TotalPages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequiresRightAlignment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c"&gt;// Arrancará en 5&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;CalculatePaginationOffsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total de planchas lógicas estimadas: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
