No description
  • JavaScript 97%
  • Shell 3%
Find a file
2026-08-06 17:20:50 -03:00
docs Agrega cards y list-grid al renderer de presentaciones 2026-08-06 17:20:50 -03:00
examples Implementa MVP del generador de presentaciones PPTX 2026-08-02 22:00:16 -03:00
output Ignora presentaciones generadas y conserva output 2026-08-02 21:59:15 -03:00
schemas Agrega cards y list-grid al renderer de presentaciones 2026-08-06 17:20:50 -03:00
src Agrega cards y list-grid al renderer de presentaciones 2026-08-06 17:20:50 -03:00
templates/osvaldo-docencia Agrega cards y list-grid al renderer de presentaciones 2026-08-06 17:20:50 -03:00
tests Agrega cards y list-grid al renderer de presentaciones 2026-08-06 17:20:50 -03:00
.gitignore Implementa MVP del generador de presentaciones PPTX 2026-08-02 22:00:16 -03:00
crear-estructura-renderer.sh Implementa MVP del generador de presentaciones PPTX 2026-08-02 22:00:16 -03:00
package-lock.json Actualiza identidad y repositorio del proyecto 2026-08-02 22:07:48 -03:00
package.json Agrega pipeline compacto de validación y renderizado 2026-08-03 00:20:52 -03:00
README.md Actualiza identidad y repositorio del proyecto 2026-08-02 22:07:48 -03:00

OZBA PPTX Generator

Generador local de presentaciones PowerPoint editables a partir de archivos JSON estructurados.

El proyecto está orientado inicialmente a la creación de material didáctico, pero su arquitectura separa:

  • el contenido de la presentación;
  • los templates visuales;
  • los layouts;
  • la validación;
  • el análisis preventivo de espacio;
  • el renderizado del archivo .pptx.

El renderer utiliza PptxGenJS y produce elementos editables dentro de PowerPoint o LibreOffice Impress.


Estado actual

El MVP permite generar presentaciones con:

  • portada;
  • separadores de sección;
  • diapositivas de contenido basadas en tablas de 1 a 4 columnas;
  • títulos, subtítulos, encabezados y contenido con jerarquía tipográfica;
  • iconos o emojis como parte del texto;
  • idea clave al pie;
  • múltiples diapositivas dentro de un solo archivo;
  • reducción controlada del cuerpo de texto;
  • detección preventiva de desbordamiento;
  • bloqueo completo del deck cuando una diapositiva necesita dividirse;
  • validación estructural y semántica;
  • pruebas automáticas.

Jerarquía tipográfica del template osvaldo-docencia

Elemento Tamaño normal
Título de contenido 28 pt, negrita
Subtítulo o introducción 23 pt, negrita y cursiva
Encabezado de tabla 19 pt, negrita
Texto de contenido 17 pt
Mínimo permitido 14 pt
Título de portada o sección hasta 30 pt

Cuando una diapositiva no entra a 17 pt, el renderer prueba 16, 15 y 14 pt. Si tampoco entra a 14 pt, devuelve needs_split y no genera un PowerPoint parcial.


Requisitos

El proyecto fue desarrollado y probado con:

  • Node.js 22;
  • npm;
  • LibreOffice Impress para inspección visual;
  • PptxGenJS;
  • Ajv con JSON Schema Draft 2020-12.

Instalación

git clone https://git.ozbanet.duckdns.org/desarrollo/ozba-pptx-gen.git
cd ozba-pptx-gen
npm install

Comprobar el estado inicial:

npm test
npm run validate:template

Comandos principales

Ejecutar las pruebas

npm test

Validar un deck

npm run validate:deck -- \
  templates/osvaldo-docencia/tests/complete-deck-with-section.example.json

Generar una presentación

npm run render -- \
  templates/osvaldo-docencia/tests/complete-deck-with-section.example.json \
  output/clase-crud.pptx

Validar el template predeterminado

npm run validate:template

Analizar una tabla sin generar el PowerPoint

npm run analyze -- \
  osvaldo-docencia \
  templates/osvaldo-docencia/tests/crud-four-columns-reduction.example.json

Códigos de salida

Código Significado
0 Operación realizada correctamente
1 Error de archivo, JSON, esquema, template o configuración
2 Contenido válido que no entra y necesita dividirse

El código 2 permite que un agente, script o integración distinga un error técnico de una decisión editorial.


Flujo general

deck.json
   ↓
deck.schema.json
   ↓
load-deck.js
   ↓
validación estructural y semántica
   ↓
carga del template y sus layouts
   ↓
análisis preventivo de cada diapositiva
   ├─ fits
   ├─ fits_with_reduction
   └─ needs_split
          ↓
si todas entran
          ↓
render-deck.js
          ↓
archivo .pptx editable

El renderer analiza todas las diapositivas antes de crear el archivo. Si una sola necesita dividirse, no se genera un deck parcial.


Contrato JSON del deck

Un deck contiene:

{
  "template": "osvaldo-docencia",
  "title": "Clase CRUD",
  "slides": []
}

Propiedades principales

Propiedad Obligatoria Descripción
template Identificador del template
title No Título general y metadato del archivo PPTX
slides Lista ordenada de diapositivas

Cada diapositiva debe tener:

  • id único;
  • type;
  • las propiedades requeridas por ese tipo.

Los identificadores deben usar minúsculas, números y guiones:

portada
seccion-operaciones-crud
crud-aplicado

Tipos de diapositiva

1. cover

Portada principal de la presentación.

{
  "id": "portada",
  "type": "cover",
  "title": "Diseño y Programación Web II",
  "metadata": [
    "CRUD aplicado a sistemas web",
    "Carrera: Ingeniería Informática",
    "Docente: Osvaldo Enrique Micniuk",
    "Año académico 2026"
  ]
}

Comportamiento

  • title se renderiza como título principal;
  • la primera línea de metadata funciona como subtítulo;
  • las líneas restantes se presentan como datos institucionales;
  • los logos y el fondo se agregan automáticamente cuando existen en el template.

2. section

Separador de capítulo o bloque temático.

{
  "id": "seccion-operaciones-crud",
  "type": "section",
  "icon": "🧩",
  "title": "Operaciones CRUD",
  "subtitle": "Crear, consultar, actualizar y eliminar información"
}

Propiedades

Propiedad Obligatoria Descripción
id Identificador único
type Debe ser section
icon No Emoji o símbolo editable
title Nombre de la sección
subtitle No Explicación breve

3. content-table

Diapositiva de contenido cuya estructura visual se organiza mediante una tabla editable.

{
  "id": "crud-aplicado",
  "type": "content-table",
  "title": "🧩 CRUD aplicado al sistema de eventos",
  "lead": "Cada evento puede gestionarse mediante las cuatro operaciones básicas del CRUD.",
  "table": {
    "rowSizing": "content",
    "columnWidths": [
      0.25,
      0.25,
      0.25,
      0.25
    ],
    "rows": []
  },
  "keyIdea": "Cada acción del usuario se traduce en una operación sobre la base de datos."
}

Propiedades

Propiedad Obligatoria Descripción
id Identificador único
type Debe ser content-table
title Título de la diapositiva
lead No Introducción o subtítulo
table Definición de filas y celdas
keyIdea No Mensaje destacado al pie

Tablas

Las tablas admiten entre 1 y 4 columnas.

Todas las filas deben tener la misma cantidad de celdas.

Ejemplo de cuatro columnas

{
  "rowSizing": "content",
  "columnWidths": [
    0.25,
    0.25,
    0.25,
    0.25
  ],
  "rows": [
    [
      {
        "role": "header",
        "text": " Crear"
      },
      {
        "role": "header",
        "text": "📋 Leer"
      },
      {
        "role": "header",
        "text": "✏️ Actualizar"
      },
      {
        "role": "header",
        "text": "❌ Eliminar"
      }
    ],
    [
      {
        "component": "icon-lines",
        "items": [
          {
            "icon": "📝",
            "text": "El usuario completa el formulario"
          },
          {
            "icon": "📤",
            "text": "Los datos se envían al servidor"
          },
          {
            "icon": "🗄️",
            "text": "PHP guarda el registro"
          }
        ]
      },
      {
        "component": "icon-lines",
        "items": [
          {
            "icon": "🔎",
            "text": "PHP consulta los eventos"
          },
          {
            "icon": "📦",
            "text": "Recupera los registros"
          },
          {
            "icon": "🌐",
            "text": "Muestra la información"
          }
        ]
      },
      {
        "component": "icon-lines",
        "items": [
          {
            "icon": "📥",
            "text": "Se cargan los datos del evento"
          },
          {
            "icon": "📝",
            "text": "El usuario modifica la información"
          },
          {
            "icon": "🔄",
            "text": "PHP actualiza el registro"
          }
        ]
      },
      {
        "component": "icon-lines",
        "items": [
          {
            "icon": "🧑‍💻",
            "text": "El usuario selecciona eliminar"
          },
          {
            "icon": "⚙️",
            "text": "PHP ejecuta la eliminación"
          },
          {
            "icon": "🗑️",
            "text": "El registro desaparece"
          }
        ]
      }
    ]
  ]
}

columnWidths

columnWidths contiene proporciones, no pulgadas.

Estas dos configuraciones son equivalentes:

[0.25, 0.25, 0.25, 0.25]
[1, 1, 1, 1]

Ejemplo con una columna más ancha:

[1, 2, 1]

La cantidad de valores debe coincidir con la cantidad de columnas de la tabla.


Modos de altura de fila

content

{
  "rowSizing": "content"
}

La altura se adapta al contenido. Es el modo predeterminado y recomendado.

fill

{
  "rowSizing": "fill"
}

Las filas de contenido distribuyen la altura disponible del layout.

custom

{
  "rowSizing": "custom",
  "rowHeights": [
    0.55,
    2.1,
    1.5
  ]
}

En modo custom:

  • debe existir una altura por fila;
  • la suma no puede superar la región de contenido.

Formatos de celda

Texto simple

Una celda puede escribirse como texto:

"Contenido de la celda"

También puede utilizar un objeto:

{
  "component": "text",
  "text": "Contenido de la celda"
}

Encabezado

{
  "role": "header",
  "text": " Crear"
}

Los encabezados usan el estilo tableHeader del tema.


Líneas con iconos

{
  "component": "icon-lines",
  "items": [
    {
      "icon": "📌",
      "text": "Primera idea"
    },
    {
      "icon": "⚙️",
      "text": "Segunda idea"
    }
  ]
}

No se usan viñetas tradicionales de PowerPoint. Los iconos forman parte del texto y permanecen editables.


Código

{
  "component": "code",
  "text": "SELECT * FROM eventos;"
}

El código utiliza la fuente configurada en:

{
  "typography": {
    "fontFamily": {
      "code": "Consolas"
    }
  }
}

Detección de espacio

El analizador estima:

  • ancho útil de cada columna;
  • cantidad de líneas por celda;
  • tamaño de fuente;
  • márgenes internos;
  • altura estimada de cada fila;
  • altura total de la tabla;
  • área disponible del layout.

Estados posibles

fits

El contenido entra con los tamaños preferidos:

{
  "status": "fits",
  "bodyFontSize": 17,
  "headerFontSize": 19
}

fits_with_reduction

El contenido entra después de reducir el tamaño:

{
  "status": "fits_with_reduction",
  "bodyFontSize": 16,
  "headerFontSize": 18
}

needs_split

El contenido no entra ni siquiera con el mínimo permitido:

{
  "status": "needs_split",
  "code": "TABLE_OVERFLOW",
  "minimumBodyFontSize": 14,
  "overflowInches": 0.334,
  "suggestion": "Dividir el contenido en dos diapositivas o reducir la cantidad de columnas."
}

El renderer no resume, elimina ni recorta contenido automáticamente.


Ejemplo de deck completo

{
  "template": "osvaldo-docencia",
  "title": "Clase CRUD",
  "slides": [
    {
      "id": "portada",
      "type": "cover",
      "title": "Diseño y Programación Web II",
      "metadata": [
        "CRUD aplicado a sistemas web",
        "Carrera: Ingeniería Informática",
        "Docente: Osvaldo Enrique Micniuk",
        "Año académico 2026"
      ]
    },
    {
      "id": "seccion-operaciones-crud",
      "type": "section",
      "icon": "🧩",
      "title": "Operaciones CRUD",
      "subtitle": "Crear, consultar, actualizar y eliminar información"
    },
    {
      "id": "crud-aplicado",
      "type": "content-table",
      "title": "🧩 CRUD aplicado al sistema de eventos",
      "lead": "Cada evento puede gestionarse mediante las cuatro operaciones básicas del CRUD.",
      "table": {
        "rowSizing": "content",
        "columnWidths": [
          0.25,
          0.25,
          0.25,
          0.25
        ],
        "rows": [
          [
            {
              "role": "header",
              "text": " Crear"
            },
            {
              "role": "header",
              "text": "📋 Leer"
            },
            {
              "role": "header",
              "text": "✏️ Actualizar"
            },
            {
              "role": "header",
              "text": "❌ Eliminar"
            }
          ],
          [
            {
              "component": "icon-lines",
              "items": [
                {
                  "icon": "📝",
                  "text": "El usuario completa el formulario"
                },
                {
                  "icon": "📤",
                  "text": "Los datos se envían al servidor"
                },
                {
                  "icon": "🗄️",
                  "text": "PHP guarda el registro"
                }
              ]
            },
            {
              "component": "icon-lines",
              "items": [
                {
                  "icon": "🔎",
                  "text": "PHP consulta los eventos"
                },
                {
                  "icon": "📦",
                  "text": "Recupera los registros"
                },
                {
                  "icon": "🌐",
                  "text": "Muestra la información"
                }
              ]
            },
            {
              "component": "icon-lines",
              "items": [
                {
                  "icon": "📥",
                  "text": "Se cargan los datos del evento"
                },
                {
                  "icon": "📝",
                  "text": "El usuario modifica la información"
                },
                {
                  "icon": "🔄",
                  "text": "PHP actualiza el registro"
                }
              ]
            },
            {
              "component": "icon-lines",
              "items": [
                {
                  "icon": "🧑‍💻",
                  "text": "El usuario selecciona eliminar"
                },
                {
                  "icon": "⚙️",
                  "text": "PHP ejecuta la eliminación"
                },
                {
                  "icon": "🗑️",
                  "text": "El registro desaparece"
                }
              ]
            }
          ]
        ]
      },
      "keyIdea": "Cada acción del usuario se traduce en una operación sobre la base de datos."
    }
  ]
}

Templates

Los templates viven en:

templates/<template-id>/

Estructura actual:

templates/
└── osvaldo-docencia/
    ├── template.json
    ├── theme.json
    ├── layouts/
    │   ├── cover.json
    │   ├── section.json
    │   └── content-table.json
    ├── assets/
    │   ├── logos/
    │   ├── backgrounds/
    │   ├── decorations/
    │   └── icons/
    ├── previews/
    └── tests/

template.json

Declara:

  • identificador;
  • versión;
  • tema;
  • layouts disponibles;
  • layout predeterminado;
  • recursos visuales.

theme.json

Define:

  • tamaño de diapositiva;
  • colores;
  • fuentes;
  • tamaños tipográficos;
  • márgenes de tabla;
  • reglas de contenido;
  • política de overflow.

Layouts

Los layouts definen regiones mediante coordenadas editables:

{
  "title": {
    "x": 0.65,
    "y": 0.22,
    "w": 12.03,
    "h": 0.55
  }
}

Las medidas de las regiones se expresan en pulgadas, como espera PptxGenJS.

Recursos opcionales

El template actual declara:

  • institutionLogo;
  • careerLogo;
  • coverBackground;
  • sectionBackground;
  • footerDecoration.

Si un recurso opcional no existe, el renderer continúa sin agregarlo.


Validaciones semánticas

Además de JSON Schema, load-deck.js verifica:

  • IDs de diapositiva duplicados;
  • layouts inexistentes;
  • regiones requeridas;
  • tablas no rectangulares;
  • cantidad de columnas admitida;
  • incompatibilidad entre columnWidths y columnas;
  • incompatibilidad entre rowHeights y filas;
  • desbordamiento de alturas personalizadas.

Ejemplo de error:

{
  "status": "error",
  "code": "DECK_SEMANTIC_INVALID",
  "details": {
    "errors": [
      {
        "code": "NON_RECTANGULAR_TABLE",
        "message": "La fila 2 contiene 3 columnas, pero la primera fila contiene 4."
      }
    ]
  }
}

Estructura del proyecto

ozba-pptx-gen/
├── package.json
├── schemas/
│   ├── deck.schema.json
│   ├── layout.schema.json
│   ├── template.schema.json
│   └── theme.schema.json
├── src/
│   ├── app/
│   │   └── load-deck.js
│   ├── renderer/
│   │   ├── render-deck.js
│   │   ├── render-example.js
│   │   └── renderers/
│   │       ├── render-cover.js
│   │       └── render-section.js
│   ├── templates/
│   │   └── load-template.js
│   └── validation/
│       └── analyze-table-fit.js
├── templates/
│   └── osvaldo-docencia/
├── tests/
│   └── mvp-renderer.test.js
└── output/

render-example.js conserva compatibilidad con las primeras pruebas y expone componentes reutilizados por render-deck.js.


Pruebas automáticas

Ejecutar:

npm test

Las pruebas actuales verifican:

  1. carga de un deck con portada, sección y contenido;
  2. generación de un PPTX completo;
  3. bloqueo total ante una diapositiva con overflow;
  4. rechazo de una tabla no rectangular.

Resultado esperado:

tests 4
pass 4
fail 0

Uso desde OpenCode

El contrato recomendado para un agente es:

  1. producir un archivo JSON compatible con deck.schema.json;
  2. validarlo;
  3. ejecutar el renderer;
  4. interpretar el código de salida;
  5. corregir el JSON cuando sea necesario.

Flujo sugerido

npm run validate:deck -- deck.json
npm run render -- deck.json output/clase.pptx

Interpretación para el agente

Código 0

La presentación fue generada.

Código 1

El agente debe corregir:

  • JSON inválido;
  • propiedad desconocida;
  • identificador duplicado;
  • tabla no rectangular;
  • layout inexistente;
  • ruta incorrecta;
  • template inválido.

Código 2

El contenido es válido, pero debe reorganizarse.

El agente debe preferir:

  1. dividir el contenido en dos diapositivas;
  2. reducir la cantidad de columnas;
  3. distribuir mejor las ideas;
  4. acortar textos solo cuando el usuario o el flujo editorial lo autoricen.

El agente no debe:

  • bajar de 14 pt;
  • eliminar contenido sin autorización;
  • resumir automáticamente;
  • permitir que una tabla invada la idea clave;
  • generar un deck parcial.

Principios de diseño

  1. Contenido y presentación separados.
  2. Todo debe permanecer editable.
  3. No ocultar errores visuales.
  4. No reducir texto por debajo de 14 pt.
  5. No recortar contenido.
  6. No generar parcialmente un deck inválido.
  7. Usar tablas como estructuras flexibles de maquetación.
  8. Preferir iconos y emojis antes que viñetas tradicionales.
  9. Mantener jerarquía tipográfica y paleta coherentes.
  10. Validar antes de renderizar.

Limitaciones actuales

El MVP todavía no incluye:

  • división automática de una diapositiva;
  • renderizado de imágenes dentro de celdas;
  • gráficos;
  • diagramas;
  • notas del presentador;
  • animaciones;
  • detección visual posterior al render;
  • CLI con opciones nombradas;
  • servicio HTTP o MCP;
  • integración directa con OpenCode;
  • publicación automática en Forgejo;
  • templates adicionales.

El estado needs_split informa el problema, pero la reorganización del contenido debe realizarla un agente o una persona.


Próximos pasos sugeridos

  1. documentar un prompt de agente para producir el JSON;
  2. crear ejemplos didácticos de 1, 2, 3 y 4 columnas;
  3. añadir logos y fondos reales al template;
  4. separar los componentes visuales internos de render-example.js;
  5. crear una CLI estable;
  6. integrar el renderer con OpenCode;
  7. añadir un servicio MCP o REST;
  8. implementar otros layouts;
  9. generar previews automáticos;
  10. probar una clase completa producida por el agente.

Licencia

Pendiente de definir.