MEGA PROMPT: Generador TSP IDC — Inyeccion de Formulario a DOCX + PPTX
# MEGA PROMPT: Generador TSP IDC — Inyeccion de Formulario a DOCX + PPTX
## CONTEXTO
Desarrollar una aplicacion PWA/HTA para egresados del Instituto de Educacion Superior Publico "Diseno y Comunicacion" (IDC) que permita completar un formulario web paso a paso y generar dos archivos descargables con los datos del formulario inyectados:
1. **Informe TSP en Word (.docx)** — documento academico completo (~25 paginas, 4 capitulos)
2. **Presentacion TSP en PowerPoint (.pptx)** — 15 diapositivas para sustentacion oral
---
## PARTE 1: ARQUITECTURA CRITICA — PLANTILLAS EMBEBIDAS COMO BASE64
### REGLA DE ORO #1: NUNCA usar `fetch()` para cargar plantillas
`fetch('InformeTSP.docx')` falla silenciosamente con `NetworkError when attempting to fetch resource` cuando se abre el archivo localmente (`file:///`). La unica solucion robusta es embeber las plantillas como variables base64 en archivos `.js` separados.
### Estructura de carga en HTML:
```html
<!-- 1. Cargar JSZip (desde CDN o local) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<!-- 2. Cargar plantillas embebidas ANTES del script principal -->
<script src="tpl-docx.js"></script> <!-- const TPL_DOCX_B64 = '...' -->
<script src="tpl-pptx.js"></script> <!-- const TPL_PPTX_B64 = '...' -->
<!-- 3. Script principal con la logica -->
<script>
// TPL_DOCX_B64 y TPL_PPTX_B64 ya estan disponibles globalmente
function generarWord(){ ... }
function generarPPTX(){ ... }
</script>
```
### Script Python para generar los archivos de plantilla:
```python
import base64, os
def embed_template(input_path, output_path, var_name):
"""Convierte un archivo binario (.docx/.pptx) a un archivo .js con variable base64"""
with open(input_path, 'rb') as f:
b64 = base64.b64encode(f.read()).decode('ascii')
with open(output_path, 'w') as f:
f.write(f"/* {os.path.basename(input_path)} embebido como base64 */\n")
f.write(f"const {var_name}='")
for i in range(0, len(b64), 120):
chunk = b64[i:i+120]
f.write(chunk)
if i + 120 < len(b64):
f.write("' +\n '")
f.write("';\n")
print(f"✓ {output_path}: {len(b64):,} chars base64")
# Uso:
embed_template('InformeTSP.docx', 'tpl-docx.js', 'TPL_DOCX_B64')
embed_template('InformeTSP.pptx', 'tpl-pptx.js', 'TPL_PPTX_B64')
```
### Helper indispensable para decodificar:
```javascript
function b64ToU8(b64){
const bin = atob(b64);
const u8 = new Uint8Array(bin.length);
for(let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
return u8;
}
```
---
## PARTE 2: ERROR CRITICO ENCONTRADO — Basura JavaScript rompe TODOS los botones
### Sintoma:
Ningun boton funciona (ni "Rellenar con ejemplo", ni "Descargar Word", ni "Limpiar"). El JavaScript se rompe silenciosamente antes de registrar los event listeners.
### Causa raiz:
Al reemplazar `<script>const TPL_DOCX_B64="..."</script>` (base64 inline) por `<script src="tpl-docx.js"></script>`, el base64 anterior quedo como basura en la siguiente linea `<script>`, rompiendo TODO el JavaScript con un error de sintaxis silencioso.
### Antes (ROTO — 137,995 caracteres de basura):
```html
<script src="tpl-docx.js"></script>
<script>B1BLAwQUAAAACAAAACEAmOGOWfsBAACFBAAAEAAAAGRvY1Byb3Bz...137995 chars...
/* ===== HELPERS ===== */
const $=s=>document.querySelector(s);
```
### Despues (CORRECTO):
```html
<script src="tpl-docx.js"></script>
<script>
/* ===== HELPERS ===== */
const $=s=>document.querySelector(s);
```
### Verificacion post-fix:
```python
# El garbage base64 NO debe estar en el HTML inline
assert 'B1BLAwQUAAAACAAAACEAmOGOWfsB' not in html_content
# El JS real debe estar intacto
assert 'function generarWord()' in html_content
assert 'function rellenarDemo()' in html_content
```
---
## PARTE 3: INYECCION EN DOCX (.docx)
### Paso 1 — Analizar la plantilla y mapear indices posicionales
Abrir `InformeTSP.docx` (es un ZIP), extraer `word/document.xml`, y listar todos los textos `<w:t>` en orden:
```python
import zipfile, re
with zipfile.ZipFile('InformeTSP.docx') as z:
xml = z.read('word/document.xml').decode('utf-8')
texts = re.findall(r'<w:t[^>]*>([^<]*)</w:t>', xml)
for i, t in enumerate(texts):
if t.strip():
print(f"[{i:3d}] {t.strip()[:100]}")
```
### Paso 2 — Mapa completo de reemplazos posicionales (38 campos)
```javascript
function procesarPlantilla(u8data, formData, rows){
return JSZip.loadAsync(u8data).then(zip => {
return zip.file('word/document.xml').async('string').then(xml => {
const d = formData;
const fnc = rows.funciones || [];
const hal = rows.hallazgos || [];
const pm = rows.planMejora || [];
const rep = new Map();
const V = (v) => (v && String(v).trim()) ? String(v).trim() : '';
const ph = (v, fb) => V(v) || fb;
/* ═══ CARATULA [indices 5-16] ═══ */
rep.set(5, d.titulo || '');
rep.set(12, d.autor || '');
rep.set(13, d.correo1 || '');
rep.set(14, d.asesor ? 'ASESOR: ' + d.asesor : '');
rep.set(16, d.anio || '2026');
/* ═══ DEDICATORIA + AGRADECIMIENTO [18, 20] ═══ */
rep.set(18, ph(d.dedicatoria, '[Espacio para la dedicatoria]'));
rep.set(20, ph(d.agradecimiento, '[Espacio para redactar los agradecimientos]'));
/* ═══ RESUMEN + INTRODUCCION [51, 53, 55] ═══ */
rep.set(51, ph(d.resumen, '[Resumen ejecutivo]'));
rep.set(53, ph(d.palabrasClave, '[Palabras clave]'));
rep.set(55, ph(d.introduccion, '[Introduccion]'));
/* ═══ CAP.1 MARCO TEORICO [64, 66, 68] ═══ */
rep.set(64, ph(d.basesTeoricas, '[Bases teoricas]'));
rep.set(66, ph(d.antecedentes, '[Antecedentes]'));
rep.set(68, ph(d.marcoConceptual, '[Marco conceptual]'));
/* ═══ CAP.2 CONTEXTO LABORAL [75, 77, 78] ═══ */
rep.set(75, ph(d.descripOrg, '[Descripcion de la organizacion]'));
rep.set(77, ph(d.organigrama, '[Organigrama]'));
rep.set(78, '[Ver anexo adjunto]');
/* ═══ MANUAL DE FUNCIONES — Tabla dinamica [84-88] ═══ */
const f0 = fnc[0] || {};
rep.set(84, ph(f0.cargo, '[Cargo desempenado]'));
rep.set(85, ph(f0.funcDesc, '[Descripcion de funciones]'));
rep.set(86, ph(f0.periodo, '[Periodo]'));
/* ═══ CAP.3 ACTIVIDAD PROFESIONAL [95-119] ═══ */
rep.set(95, ph(d.target, '[Brief y audiencia objetivo]'));
rep.set(96, d.target ? 'Target / Perfil del consumidor: ' + d.target : '[Target]');
rep.set(97, d.forecasting ? 'Forecasting de tendencias: ' + d.forecasting : '[Forecasting]');
rep.set(98, d.objComercial ? 'Objetivo comercial / creativo: ' + d.objComercial : '[Objetivo comercial]');
rep.set(103, ph(d.concepto, '[Concepto creativo audiovisual]'));
rep.set(105, ph(d.paleta, '[Paleta visual y direccion de color]'));
rep.set(113, ph(d.fase1, '[Fase 1: Preproduccion]'));
rep.set(115, ph(d.fase2, '[Fase 2: Rodaje]'));
rep.set(117, ph(d.fase3, '[Fase 3: Posproduccion]'));
rep.set(119, ph(d.fase4, '[Fase 4: Distribucion]'));
/* ═══ CAP.4 EVALUACION [136, 143-170] ═══ */
rep.set(136, ph(d.analisisProductos, '[Analisis de productos desarrollados]'));
/* Hallazgos: 3 filas x 3 columnas, salto de 4 indices por fila */
for(let i = 0; i < 3; i++){
const h = hal[i] || {};
const base = 143 + i * 4;
rep.set(base, ph(h.texto, '[Hallazgo ' + (i+1) + ']'));
rep.set(base + 1, ph(h.area, '[Area]'));
rep.set(base + 2, ph(h.impacto, '[Impacto]'));
}
/* Plan de mejora: 3 filas x 4 columnas, salto de 4 indices por fila */
for(let i = 0; i < 3; i++){
const p = pm[i] || {};
const base = 159 + i * 4;
rep.set(base, ph(p.proceso, '[Proceso ' + (i+1) + ']'));
rep.set(base + 1, ph(p.situacion, '[Situacion actual]'));
rep.set(base + 2, ph(p.mejora, '[Mejora propuesta]'));
rep.set(base + 3, ph(p.plazo, '[Plazo]'));
}
/* ═══ CONCLUSIONES + RECOMENDACIONES [172-183] ═══ */
rep.set(172, ph(d.conclusion1, '[Conclusion 1]'));
rep.set(173, ph(d.conclusion2, '[Conclusion 2]'));
rep.set(174, ph(d.conclusion3, '[Conclusion 3]'));
rep.set(177, ph(d.recSector, '[Recomendacion al sector]'));
rep.set(179, ph(d.recEmpresa, '[Recomendacion a la empresa]'));
rep.set(181, ph(d.recProfesionales, '[Recomendacion a profesionales]'));
rep.set(183, ph(d.recAcademia, '[Recomendacion a la academia]'));
/* ═══ APLICAR REEMPLAZOS POSICIONALES ═══ */
let idx = 0;
let newXml = xml.replace(/(<w:t[^>]*>)([^<]*)(<\/w:t>)/g, (m, open, body, close) => {
const rText = rep.get(idx);
idx++;
return (rText !== undefined) ? (open + escXML(rText) + close) : m;
});
/* ═══ REEMPLAZO ESPECIAL: Referencias APA (bloque completo) ═══ */
if(d.referencias && d.referencias.trim()){
const refs = d.referencias.trim().split('\n').filter(l => l.trim());
let refXml = '';
refs.forEach(r => {
refXml += '<w:p><w:pPr><w:spacing w:after="120" w:line="360" w:lineRule="auto"/>'
+ '<w:ind w:left="708" w:hanging="708"/><w:jc w:val="both"/></w:pPr>'
+ '<w:r><w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>'
+ '<w:sz w:val="22"/><w:szCs w:val="22"/></w:rPr>'
+ '<w:t xml:space="preserve">' + escXML(r.trim()) + '</w:t></w:r></w:p>';
});
const refStart = newXml.indexOf('>Bruzzi, S.');
if(refStart > 0){
const pStart = newXml.lastIndexOf('<w:p ', refStart);
const anexosIdx = newXml.indexOf('ANEXOS O APENDICES');
const pEnd = newXml.lastIndexOf('</w:p>', anexosIdx) + 6;
if(pStart > 0 && pEnd > pStart){
newXml = newXml.substring(0, pStart) + refXml + newXml.substring(pEnd);
}
}
}
zip.file('word/document.xml', newXml);
return zip.generateAsync({type:'uint8array', compression:'DEFLATE', compressionOptions:{level:6}});
});
});
}
```
### Helper escXML (escapar XML):
```javascript
function escXML(s){
return String(s || '').replace(/&/g,'&')
.replace(/</g,'<')
.replace(/>/g,'>')
.replace(/"/g,'"')
.replace(/'/g,''');
}
```
---
## PARTE 4: INYECCION EN PPTX (.pptx)
### Estrategia identica: reemplazo posicional por slide
```javascript
function generarPPTX(){
/* Validaciones minimas */
const d = recolectar();
const err = validarGeneracion(d);
if(err){ mostrarToast('✕ ' + err, 'err'); return; }
mostrarToast('Generando presentacion PPTX...', 'ai');
const u8tpl = b64ToU8(TPL_PPTX_B64);
if(typeof JSZip === 'undefined'){
cargarJSZip(() => procesarPlantillaPPTX(u8tpl, d));
} else {
procesarPlantillaPPTX(u8tpl, d);
}
}
function procesarPlantillaPPTX(u8tpl, d){
JSZip.loadAsync(u8tpl).then(zip => {
const repMap = pptxSlideReplacements(d, State.rows);
/* Reemplazar textos en cada slide (1-15) */
const slidePromises = [];
for(let n = 1; n <= 15; n++){
const file = zip.file('ppt/slides/slide' + n + '.xml');
if(!file) continue;
slidePromises.push(
file.async('string').then(xml => {
const newXml = pptxApplyReplacements(xml, repMap[n] || []);
zip.file('ppt/slides/slide' + n + '.xml', newXml);
})
);
}
return Promise.all(slidePromises).then(() => zip);
}).then(zip => {
return zip.generateAsync({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
compression: 'DEFLATE',
compressionOptions: {level: 6}
});
}).then(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
const ap = (d.autor || 'TSP').split(/\s+/)[0].replace(/[^a-zA-Z0-9_-]/g, '') || 'TSP';
a.download = 'SustentacionTSP_ComAudiovisual_' + ap + '_' + (d.anio || '2026') + '.pptx';
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 3000);
mostrarToast('✓ Presentacion PPTX generada', 'ok');
}).catch(e => {
console.error(e);
mostrarToast('✕ Error: ' + e.message, 'err');
});
}
/* Reemplazo posicional en slides PPTX
En PPTX los textos estan en <a:t>...</a:t> (DrawingML) */
function pptxApplyReplacements(xml, repls){
const map = new Map(repls.map(r => [r[0], r[1]]));
let i = 0;
return xml.replace(/(<a:t[^>]*>)([^<]*)(<\/a:t>)/g, (m, open, body, close) => {
const t = map.get(i);
i++;
return (t === undefined) ? m : (open + escXML(t) + close);
});
}
/* Mapa de reemplazos por slide */
function pptxSlideReplacements(d, rows){
const ph = (v, fb) => (v && !esPlaceholder(v)) ? String(v).trim() : fb;
const ap = (d.autor || 'Autor').split(/\s+/).slice(0, 2).join(' ');
const footer = ap + ' · TSP ComAudiovisual IDC · ' + (d.anio || '2026');
return {
1: [[2, ph(d.titulo, '[Titulo del TSP]')],
[3, (d.autor || '') + (d.autor2 ? ' · ' + d.autor2 : '')],
[4, 'Asesor: ' + (d.asesor || '[Asesor]') + ' · ' + (d.semestre || '') + ' · Lima – Peru ' + (d.anio || '2026')]],
2: [[2, ph(d.dedicatoria, '[Dedicatoria]')], [3, footer]],
3: [[2, ph(d.agradecimiento, '[Agradecimiento]')], [4, footer]],
4: [[2, ph(d.resumen, '[Resumen ejecutivo]')],
[3, 'Palabras clave: ' + ph(d.palabrasClave, '[...]')],
[5, footer]],
5: [[2, ph(d.introduccion, '[Introduccion]')], [3, footer]],
6: [[2, ph(d.basesTeoricas, '[Bases teoricas]')],
[3, ph(d.antecedentes, '[Antecedentes]')],
[4, ph(d.marcoConceptual, '[Marco conceptual]')],
[6, footer]],
7: [[2, ph(d.descripOrg, '[Descripcion de la organizacion]')],
[3, ph(d.organigrama, '[Organigrama]')],
[5, footer]],
8: [[2, ph(d.target, '[Brief y audiencia]')],
[3, ph(d.forecasting, '[Forecasting]')],
[4, ph(d.objComercial, '[Objetivo comercial]')],
[6, footer]],
9: [[2, ph(d.concepto, '[Concepto creativo]')],
[3, ph(d.paleta, '[Paleta visual]')],
[4, ph(d.texturas, '[Texturas y materiales]')],
[6, footer]],
10: [[2, ph(d.fase1, '[Fase 1: Preproduccion]')],
[3, ph(d.fase2, '[Fase 2: Rodaje]')],
[5, footer]],
11: [[2, ph(d.fase3, '[Fase 3: Posproduccion]')],
[3, ph(d.fase4, '[Fase 4: Distribucion]')],
[4, ph(d.evidencias, '[Evidencias]')],
[6, footer]],
12: [[2, ph(d.analisisProductos, '[Analisis de productos]')],
[3, (rows.hallazgos || []).map(h => h.texto).join(' · ')],
[5, footer]],
13: [[2, ph(d.conclusion1, '[Conclusion 1]')],
[3, ph(d.conclusion2, '[Conclusion 2]')],
[4, ph(d.conclusion3, '[Conclusion 3]')],
[6, footer]],
14: [[2, ph(d.recSector, '[Rec. al sector]')],
[3, ph(d.recEmpresa, '[Rec. a la empresa]')],
[4, ph(d.recProfesionales, '[Rec. a profesionales]')],
[5, ph(d.recAcademia, '[Rec. a la academia]')],
[7, footer]],
15: [[2, 'REFERENCIAS BIBLIOGRAFICAS'],
[3, (d.referencias || '').split('\n').slice(0, 3).join('\n')],
[5, footer]],
};
}
```
---
## PARTE 5: VALIDACIONES PRE-GENERACION
```javascript
function validarGeneracion(d){
if(!d.titulo || d.titulo.trim().length < 5)
return 'El titulo del TSP es obligatorio (minimo 5 caracteres)';
if(d.titulo.trim().split(/\s+/).length > 25)
return 'El titulo no debe superar 25 palabras';
if(!d.autor || d.autor.trim().split(/\s+/).length < 2)
return 'Debe registrar al menos un autor con nombre completo';
if(!d.asesor || !d.asesor.trim())
return 'El nombre del asesor es obligatorio';
if(!d.resumen || !d.resumen.trim())
return 'El Resumen Ejecutivo es obligatorio';
if(d.resumen.trim().split(/\s+/).length > 250)
return 'El Resumen no debe superar 250 palabras';
if(State.rows.funciones.filter(r => r.cargo || r.funcDesc).length < 1)
return 'Debe registrar al menos una funcion en el Manual de Funciones';
return null; // Todo OK
}
```
---
## PARTE 6: FORMULARIO HTML (40+ campos, 8 pasos)
```
PASO 1 — Caratula: titulo, autor, dni, correo1, autor2, dni2, correo2, asesor, anio, semestre, linea
PASO 2 — Dedicatoria: dedicatoria, agradecimiento
PASO 3 — Resumen: resumen, palabrasClave, introduccion
PASO 4 — Cap.1: basesTeoricas, antecedentes, marcoConceptual
PASO 5 — Cap.2: descripOrg, organigrama + tabla funciones[{cargo, funcDesc, periodo}]
PASO 6 — Cap.3: target, forecasting, objComercial, concepto, paleta, texturas, materiales,
avios, tecnologia, fase1, fase2, fase3, fase4, evidencias
PASO 7 — Cap.4: analisisProductos + tabla hallazgos[{num, texto, area, impacto}]
+ tabla planMejora[{proceso, situacion, mejora, plazo}]
PASO 8 — Cierre: conclusion1, conclusion2, conclusion3, recSector, recEmpresa,
recProfesionales, recAcademia, referencias
```
---
## PARTE 7: ESTRUCTURA DE ARCHIVOS FINAL
```
/output/
|-- index.html # PWA principal (~305 KB limpio)
|-- tpl-docx.js # InformeTSP.docx embebido en base64 (~173 KB)
|-- tpl-pptx.js # InformeTSP.pptx embebido en base64
|-- sw.js # Service Worker (opcional)
|-- manifest.json # Manifest PWA (opcional)
|-- Logo_IDC.png # Logo del IDC
`-- InformeTSP.docx # Plantilla original (referencia)
```
---
## PARTE 8: REGLAS DE ORO
| # | Regla | Por que |
|---|-------|---------|
| 1 | **NUNCA `fetch()`** para plantillas | Falla con `file:///` -> embeber base64 en `.js` |
| 2 | **NUNCA buscar/reemplazar por texto** en XML | Usar indices posicionales de `<w:t>` / `<a:t>` |
| 3 | **SIEMPRE escapar XML** con `escXML()` | Evita romper el XML con `&`, `<`, `>` del usuario |
| 4 | **SIEMPRE preservar** archivos internos no modificados | No tocar `media/`, `theme/`, `styles.xml`, etc. |
| 5 | **SIEMPRE usar `DEFLATE`** al recomprimir el ZIP | Compresion requerida por Office |
| 6 | **SIEMPRE limpiar** basura al cambiar de estrategia | El base64 inline antiguo rompio TODO el JS |
| 7 | **SIEMPRE revocar** `URL.createObjectURL()` | `setTimeout(() => URL.revokeObjectURL(href), 3000)` |
---
## PARTE 9: CHECKLIST DE VERIFICACION
- [ ] DOCX: Analizar plantilla y mapear todos los indices `<w:t>`
- [ ] DOCX: Crear `tpl-docx.js` con base64 embebido
- [ ] DOCX: Implementar reemplazos posicionales con `Map()`
- [ ] DOCX: Implementar reemplazo de bloque para referencias APA
- [ ] PPTX: Analizar plantilla y mapear indices `<a:t>` por slide
- [ ] PPTX: Crear `tpl-pptx.js` con base64 embebido
- [ ] PPTX: Implementar `pptxApplyReplacements()` posicional
- [ ] CRITICO: Verificar que NO haya basura base64 en el `<script>` inline
- [ ] CRITICO: Verificar que `TPL_DOCX_B64` se carga desde `tpl-docx.js` externo
- [ ] Validaciones: Verificar campos obligatorios antes de generar
- [ ] Test: "Rellenar con ejemplo" -> llena todos los campos
- [ ] Test: "Descargar Word" -> genera .docx con datos inyectados
- [ ] Test: "Descargar PPTX" -> genera .pptx con datos inyectados
- [ ] Test: Verificar que estilos, imagenes y tablas se preservan intactos
---
## PARTE 10: FLUJO DE DATOS COMPLETO
```
+-----------------+ +------------------+
| FORMULARIO HTML |---->| recolectar() |
| (40+ campos) | | (validaciones) |
+-----------------+ +--------+---------+
|
+------------+------------+
| | |
v v v
+---------+ +---------+ +---------+
| tpl-docx| | tpl-pptx| | UI |
| .js | | .js | | toast |
+----+----+ +----+----+ +---------+
| |
+----+----+ +----+----+
|b64ToU8()| |b64ToU8()|
+----+----+ +----+----+
| |
+----+----+ +----+----+
|JSZip | |JSZip |
|loadAsync| |loadAsync|
+----+----+ +----+----+
| |
+----+---------+ +--+---------+
|reemplazos | |reemplazos |
|posicionales | |por slide |
|Map() <w:t> | |Map() <a:t> |
+----+---------+ +----+-------+
| |
+----+---------+ +----+-------+
|generateAsync | |generateAsync|
|(uint8array) | |(blob) |
+----+---------+ +----+-------+
| |
+----+---------+ +----+-------+
|Blob + | |Blob + |
|descarga | |descarga |
|.docx | |.pptx |
+--------------+ +-------------+
```
Comentarios
Publicar un comentario