- JavaScript 97%
- Shell 3%
| docs | ||
| examples | ||
| output | ||
| schemas | ||
| src | ||
| templates/osvaldo-docencia | ||
| tests | ||
| .gitignore | ||
| crear-estructura-renderer.sh | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
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 |
Sí | Identificador del template |
title |
No | Título general y metadato del archivo PPTX |
slides |
Sí | 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
titlese renderiza como título principal;- la primera línea de
metadatafunciona 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 |
Sí | Identificador único |
type |
Sí | Debe ser section |
icon |
No | Emoji o símbolo editable |
title |
Sí | 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 |
Sí | Identificador único |
type |
Sí | Debe ser content-table |
title |
Sí | Título de la diapositiva |
lead |
No | Introducción o subtítulo |
table |
Sí | 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
columnWidthsy columnas; - incompatibilidad entre
rowHeightsy 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:
- carga de un deck con portada, sección y contenido;
- generación de un PPTX completo;
- bloqueo total ante una diapositiva con overflow;
- rechazo de una tabla no rectangular.
Resultado esperado:
tests 4
pass 4
fail 0
Uso desde OpenCode
El contrato recomendado para un agente es:
- producir un archivo JSON compatible con
deck.schema.json; - validarlo;
- ejecutar el renderer;
- interpretar el código de salida;
- 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:
- dividir el contenido en dos diapositivas;
- reducir la cantidad de columnas;
- distribuir mejor las ideas;
- 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
- Contenido y presentación separados.
- Todo debe permanecer editable.
- No ocultar errores visuales.
- No reducir texto por debajo de 14 pt.
- No recortar contenido.
- No generar parcialmente un deck inválido.
- Usar tablas como estructuras flexibles de maquetación.
- Preferir iconos y emojis antes que viñetas tradicionales.
- Mantener jerarquía tipográfica y paleta coherentes.
- 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
- documentar un prompt de agente para producir el JSON;
- crear ejemplos didácticos de 1, 2, 3 y 4 columnas;
- añadir logos y fondos reales al template;
- separar los componentes visuales internos de
render-example.js; - crear una CLI estable;
- integrar el renderer con OpenCode;
- añadir un servicio MCP o REST;
- implementar otros layouts;
- generar previews automáticos;
- probar una clase completa producida por el agente.
Licencia
Pendiente de definir.