g360-cli 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -27
- package/package.json +1 -1
- package/src/assets/pptx/design-tokens.js +124 -0
- package/src/assets/pptx/layouts/base.js +357 -155
- package/src/assets/pptx/layouts/index.js +6 -1
- package/src/assets/pptx/scripts/analyze-app.js +177 -13
- package/src/assets/pptx/scripts/image-size.js +56 -0
- package/src/assets/pptx/templates/app-manual.js +324 -202
- package/src/assets/pptx/themes/base.js +6 -61
- package/src/assets/pptx/themes/cipsa.js +2 -44
- package/src/assets/pptx/themes/g360.js +2 -30
- package/src/assets/pptx/themes/index.js +87 -2
- package/src/commands/pptx.js +9 -7
|
@@ -1,275 +1,397 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template Manual de App G360 —
|
|
3
|
-
*
|
|
2
|
+
* Template Manual de App G360 — formato 16:9 widescreen, estilo ventas-pulse.
|
|
3
|
+
*
|
|
4
4
|
* Estructura:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5-N. Funcionalidades (una por
|
|
10
|
-
* N+1. Flujos de trabajo (
|
|
5
|
+
* 1. Portada
|
|
6
|
+
* 2. ¿Qué es? (descripcion + tarjetas usuario/tecnico)
|
|
7
|
+
* 3. Módulos (tabla de modulos + capturas)
|
|
8
|
+
* 4. Flujo de trabajo (pasos numerados + capturas)
|
|
9
|
+
* 5-N. Funcionalidades (una por modulo UI detectado)
|
|
10
|
+
* N+1. Flujos de trabajo (modales)
|
|
11
11
|
* N+2. Arquitectura
|
|
12
12
|
* N+3. Buenas prácticas
|
|
13
|
-
* N+4.
|
|
13
|
+
* N+4. Límites conocidos (si el proyecto define appData.limits)
|
|
14
|
+
* N+5. Resumen
|
|
14
15
|
*/
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { createTheme } from '../themes/index.js';
|
|
17
18
|
import {
|
|
18
19
|
coverSlide,
|
|
19
|
-
|
|
20
|
+
addSectionHeader,
|
|
21
|
+
addFooter,
|
|
22
|
+
card,
|
|
23
|
+
phoneShot,
|
|
20
24
|
featureSlide,
|
|
21
25
|
workflowSlide,
|
|
22
|
-
|
|
26
|
+
limitsSlide,
|
|
23
27
|
checklistSlide,
|
|
24
28
|
kpiSlide,
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
architectureSlide,
|
|
30
|
+
modulesTable,
|
|
27
31
|
} from '../layouts/base.js';
|
|
28
32
|
|
|
33
|
+
const MAX_FEATURES = 6;
|
|
34
|
+
|
|
35
|
+
/** Descripciones genericas por clase UI conocida */
|
|
36
|
+
const FEATURE_DESC = {
|
|
37
|
+
KpiCard: 'Indicadores clave del negocio en tiempo real, con estados de alerta y recuento por categoría.',
|
|
38
|
+
Dashboard: 'Vista principal con el resumen operativo: KPIs, listados y accesos a los módulos de trabajo.',
|
|
39
|
+
WarehouseCard: 'Tarjeta de almacén con existencias y estado por depósito.',
|
|
40
|
+
SearchOverlay: 'Buscador flotante global: localiza registros de cualquier módulo sin salir de la vista.',
|
|
41
|
+
ExportModal: 'Exportación a Excel con formato corporativo para seguimiento y auditoría.',
|
|
42
|
+
SkuDetailModal: 'Detalle de SKU: existencias, movimientos y atributos relevantes.',
|
|
43
|
+
TrasladosModal: 'Traslados entre almacenes con validación de stock disponible.',
|
|
44
|
+
SinStockModal: 'Productos sin stock: prioriza reposición y alternativas.',
|
|
45
|
+
AppSidebar: 'Barra lateral de navegación entre módulos.',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function featureDesc(feature) {
|
|
49
|
+
return feature.desc
|
|
50
|
+
|| FEATURE_DESC[feature.name]
|
|
51
|
+
|| `Módulo de interfaz ubicado en src/ui/${feature.file}, integrado al flujo principal de la aplicación.`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function classify(feature) {
|
|
55
|
+
if (feature.name.includes('Kpi') || feature.name.includes('Card')) return 'kpi';
|
|
56
|
+
if (feature.name.includes('Modal') || feature.name.includes('Dialog')) return 'workflow';
|
|
57
|
+
if (feature.name.includes('Dashboard')) return 'screenshot';
|
|
58
|
+
return 'feature';
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
/**
|
|
30
|
-
* Genera un manual PPTX completo para una app G360.
|
|
31
|
-
*
|
|
62
|
+
* Genera un manual PPTX completo para una app G360 (16:9).
|
|
63
|
+
*
|
|
32
64
|
* @param {object} appData — resultado de analyzeApp()
|
|
33
|
-
* @param {object} options — { mode, theme,
|
|
65
|
+
* @param {object} options — { mode, theme, outPath }
|
|
34
66
|
* @returns {Promise<string>} — ruta del archivo .pptx generado
|
|
35
67
|
*/
|
|
36
68
|
export async function generateManualPptx(appData, options = {}) {
|
|
37
|
-
const { mode = 'manual'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const theme = themeName === 'cipsa' || (!themeName && appData.brand === 'cipsa')
|
|
41
|
-
? createCipsaTheme()
|
|
42
|
-
: createG360Theme();
|
|
43
|
-
|
|
69
|
+
const { mode = 'manual' } = options;
|
|
70
|
+
const themeName = options.theme || appData.brand || 'g360';
|
|
71
|
+
const theme = createTheme(themeName === 'cipsa' ? 'cipsa' : themeName);
|
|
44
72
|
const { pptx, colors } = theme;
|
|
45
|
-
|
|
46
|
-
// Configurar tamaño segun modo
|
|
47
|
-
if (mode === 'demo') {
|
|
48
|
-
pptx.layout = 'LAYOUT_16x9';
|
|
49
|
-
} else {
|
|
50
|
-
pptx.layout = 'A4';
|
|
51
|
-
}
|
|
73
|
+
const S = theme.spacing;
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
const appName = appData.name || 'Mi Aplicación G360';
|
|
76
|
+
const features = appData.features.slice(0, MAX_FEATURES);
|
|
77
|
+
const hasModals = (appData.hasModals?.length || 0) > 0;
|
|
78
|
+
const hasLimits = Array.isArray(appData.limits) && appData.limits.length > 0;
|
|
79
|
+
|
|
80
|
+
const totalSlides = 4
|
|
81
|
+
+ features.length
|
|
82
|
+
+ (hasModals ? 1 : 0)
|
|
83
|
+
+ 3
|
|
84
|
+
+ (hasLimits ? 1 : 0);
|
|
85
|
+
|
|
86
|
+
let slideNum = 0;
|
|
87
|
+
const bg = { color: colors.bg };
|
|
88
|
+
const newSlide = () => {
|
|
89
|
+
const s = pptx.addSlide();
|
|
90
|
+
s.background = bg;
|
|
91
|
+
return s;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const screenshotFor = (feature) => {
|
|
95
|
+
if (feature.screenshotIndex >= 0 && appData.screenshots?.[feature.screenshotIndex]) {
|
|
96
|
+
return appData.screenshots[feature.screenshotIndex].path;
|
|
97
|
+
}
|
|
98
|
+
const shot = appData.screenshots?.find((s) =>
|
|
99
|
+
s.filename.toLowerCase().includes(feature.name.toLowerCase().slice(0, 4)),
|
|
100
|
+
);
|
|
101
|
+
return shot?.path || null;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ===== 1. Portada =====
|
|
105
|
+
let slide = newSlide();
|
|
56
106
|
coverSlide(slide, {
|
|
57
|
-
appName
|
|
58
|
-
description: appData.description || '
|
|
107
|
+
appName,
|
|
108
|
+
description: appData.description || 'Documentación de uso y funcionalidades',
|
|
109
|
+
tagline: buildTagline(appData),
|
|
59
110
|
version: appData.version,
|
|
60
|
-
brand:
|
|
111
|
+
brand: themeName,
|
|
61
112
|
}, theme);
|
|
62
|
-
addFooter(slide, theme,
|
|
113
|
+
addFooter(slide, theme, ++slideNum, totalSlides);
|
|
63
114
|
|
|
64
|
-
// =====
|
|
65
|
-
slide =
|
|
66
|
-
slide
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
115
|
+
// ===== 2. ¿Qué es? =====
|
|
116
|
+
slide = newSlide();
|
|
117
|
+
addSectionHeader(slide, 'INTRODUCCIÓN', theme, ++slideNum, totalSlides);
|
|
118
|
+
slide.addText(`¿Qué es ${appName}?`, {
|
|
119
|
+
x: S.marginX, y: 1.05, w: S.contentWidth, h: 0.45,
|
|
120
|
+
fontSize: 15, bold: true, color: colors.text, fontFace: theme.font,
|
|
121
|
+
});
|
|
122
|
+
slide.addText(appData.description || 'Aplicación construida con los estándares G360.', {
|
|
123
|
+
x: S.marginX, y: 1.5, w: S.contentWidth, h: 0.85,
|
|
124
|
+
fontSize: theme.typo.sizes.body, color: colors.text,
|
|
125
|
+
valign: 'top', fontFace: theme.font,
|
|
126
|
+
});
|
|
127
|
+
card(slide, {
|
|
128
|
+
x: S.marginX, y: 2.55, w: (S.contentWidth - S.cardGap) / 2, h: 2.0,
|
|
129
|
+
title: 'Para el usuario',
|
|
130
|
+
body: features.length > 0
|
|
131
|
+
? features.slice(0, 4).map((f) => f.display || f.name)
|
|
132
|
+
: ['Acceso a los módulos principales de la app'],
|
|
77
133
|
}, theme);
|
|
78
|
-
|
|
134
|
+
card(slide, {
|
|
135
|
+
x: S.marginX + (S.contentWidth - S.cardGap) / 2 + S.cardGap, y: 2.55,
|
|
136
|
+
w: (S.contentWidth - S.cardGap) / 2, h: 2.0,
|
|
137
|
+
title: 'Bajo el capó',
|
|
138
|
+
body: buildTechBullets(appData, features),
|
|
139
|
+
}, theme);
|
|
140
|
+
slide.addText(buildCapabilityLine(appData), {
|
|
141
|
+
x: S.marginX, y: 4.85, w: S.contentWidth, h: 1.6,
|
|
142
|
+
fontSize: theme.typo.sizes.body, color: colors.textMuted, valign: 'top', fontFace: theme.font,
|
|
143
|
+
});
|
|
79
144
|
|
|
80
|
-
// =====
|
|
81
|
-
slide =
|
|
82
|
-
slide
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
workflowSlide(slide, {
|
|
90
|
-
title: 'Como iniciar la aplicacion',
|
|
91
|
-
steps: installSteps,
|
|
145
|
+
// ===== 3. Módulos =====
|
|
146
|
+
slide = newSlide();
|
|
147
|
+
addSectionHeader(slide, 'MÓDULOS', theme, ++slideNum, totalSlides);
|
|
148
|
+
modulesTable(slide, {
|
|
149
|
+
rows: features.map((f) => ({
|
|
150
|
+
name: f.display || f.name,
|
|
151
|
+
route: f.file,
|
|
152
|
+
desc: featureDesc(f),
|
|
153
|
+
})),
|
|
92
154
|
}, theme);
|
|
93
|
-
|
|
155
|
+
const shots = features.map(screenshotFor).filter(Boolean);
|
|
156
|
+
if (shots[0]) {
|
|
157
|
+
phoneShot(slide, {
|
|
158
|
+
x: S.page.width - S.marginX - 3.95, y: S.contentTopY,
|
|
159
|
+
w: 1.85, h: 4.0, imagePath: shots[0],
|
|
160
|
+
label: `Captura de ${features[0].display || features[0].name}`,
|
|
161
|
+
}, theme);
|
|
162
|
+
}
|
|
163
|
+
if (shots[1]) {
|
|
164
|
+
phoneShot(slide, {
|
|
165
|
+
x: S.page.width - S.marginX - 1.9, y: S.contentTopY,
|
|
166
|
+
w: 1.85, h: 4.0, imagePath: shots[1],
|
|
167
|
+
label: `Captura de ${features[1]?.display || 'módulo'}`,
|
|
168
|
+
}, theme);
|
|
169
|
+
}
|
|
170
|
+
slide.addText('Cada módulo se detalla en las siguientes páginas.', {
|
|
171
|
+
x: S.marginX, y: S.contentTopY + features.length * 1.12 + 0.1, w: S.contentWidth - 4.35, h: 0.5,
|
|
172
|
+
fontSize: theme.typo.sizes.caption, color: colors.textMuted, fontFace: theme.font,
|
|
173
|
+
});
|
|
94
174
|
|
|
95
|
-
// =====
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
slide = pptx.addSlide();
|
|
102
|
-
slide.background = { color: colors.bg };
|
|
103
|
-
addSectionHeader(slide, 'PRIMERA VISTA', theme);
|
|
104
|
-
const dashScreen = appData.screenshots?.find(s => s.filename.toLowerCase().includes('dash'));
|
|
105
|
-
screenshotSlide(slide, {
|
|
106
|
-
title: 'Dashboard Principal',
|
|
107
|
-
description: appData.hasAutoRefresh
|
|
108
|
-
? 'Vista principal con KPIs en tiempo real y auto-refresh cada 15 minutos.'
|
|
109
|
-
: 'Vista principal de la aplicacion con los indicadores clave del negocio.',
|
|
110
|
-
screenshotPath: dashScreen?.path,
|
|
175
|
+
// ===== 4. Flujo de trabajo =====
|
|
176
|
+
slide = newSlide();
|
|
177
|
+
addSectionHeader(slide, 'FLUJO DE TRABAJO', theme, ++slideNum, totalSlides);
|
|
178
|
+
workflowSlide(slide, {
|
|
179
|
+
textWidth: S.contentWidth - 0.65 - 4.9,
|
|
180
|
+
steps: buildWorkflowSteps(appData),
|
|
111
181
|
}, theme);
|
|
112
|
-
|
|
113
|
-
|
|
182
|
+
if (shots[2]) {
|
|
183
|
+
phoneShot(slide, {
|
|
184
|
+
x: S.page.width - S.marginX - 4.75, y: S.contentTopY + 0.15,
|
|
185
|
+
w: 2.25, h: 4.9, imagePath: shots[2],
|
|
186
|
+
label: 'Pantalla de inicio / login',
|
|
187
|
+
}, theme);
|
|
188
|
+
}
|
|
189
|
+
if (shots[3]) {
|
|
190
|
+
phoneShot(slide, {
|
|
191
|
+
x: S.page.width - S.marginX - 2.3, y: S.contentTopY + 0.15,
|
|
192
|
+
w: 2.25, h: 4.9, imagePath: shots[3],
|
|
193
|
+
label: 'Vista principal en uso',
|
|
194
|
+
}, theme);
|
|
195
|
+
}
|
|
114
196
|
|
|
115
|
-
// Features
|
|
116
|
-
for (const feature of
|
|
117
|
-
slide =
|
|
118
|
-
slide
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
const classification = classifyFeatureForSlide(feature);
|
|
126
|
-
|
|
127
|
-
if (classification === 'kpi' && feature.name.includes('Kpi')) {
|
|
197
|
+
// ===== 5-N. Features =====
|
|
198
|
+
for (const feature of features) {
|
|
199
|
+
slide = newSlide();
|
|
200
|
+
addSectionHeader(slide, 'FUNCIONALIDAD', theme, ++slideNum, totalSlides);
|
|
201
|
+
const kind = classify(feature);
|
|
202
|
+
const shotPath = screenshotFor(feature);
|
|
203
|
+
const textW = shotPath ? S.contentWidth - 5.1 : S.contentWidth;
|
|
204
|
+
|
|
205
|
+
if (kind === 'kpi') {
|
|
128
206
|
kpiSlide(slide, {
|
|
129
207
|
title: feature.display || feature.name,
|
|
130
|
-
kpis:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
208
|
+
kpis: [
|
|
209
|
+
{ label: 'Registros', value: '—', color: colors.accent },
|
|
210
|
+
{ label: 'Activos', value: '—', color: colors.success },
|
|
211
|
+
{ label: 'Alertas', value: '—', color: colors.warning },
|
|
212
|
+
{ label: 'Críticos', value: '—', color: colors.danger },
|
|
213
|
+
],
|
|
136
214
|
}, theme);
|
|
137
215
|
} else {
|
|
138
216
|
featureSlide(slide, {
|
|
139
217
|
title: feature.display || feature.name,
|
|
140
|
-
description:
|
|
141
|
-
bullets:
|
|
142
|
-
'
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
218
|
+
description: featureDesc(feature),
|
|
219
|
+
bullets: kind === 'workflow'
|
|
220
|
+
? ['Se abre desde el módulo principal', 'Interacción guiada paso a paso', 'Confirmación con resumen del resultado']
|
|
221
|
+
: feature.kind === 'route'
|
|
222
|
+
? [
|
|
223
|
+
`Ruta accesible desde la navegación principal (${feature.file})`,
|
|
224
|
+
'Estado sincronizado con los datos del negocio',
|
|
225
|
+
'Diseño responsive y táctil',
|
|
226
|
+
]
|
|
227
|
+
: feature.kind === 'component'
|
|
228
|
+
? [
|
|
229
|
+
`Componente web en ${feature.file}`,
|
|
230
|
+
'Integrado al flujo principal de la app',
|
|
231
|
+
'Reutilizable y con estados de carga/error',
|
|
232
|
+
]
|
|
233
|
+
: [
|
|
234
|
+
`Clase ${feature.name} exportada desde src/ui/${feature.file}`,
|
|
235
|
+
'Integrada al Dashboard principal',
|
|
236
|
+
'Respeta el tema claro/oscuro del sistema',
|
|
237
|
+
],
|
|
146
238
|
}, theme);
|
|
239
|
+
// limitar ancho del texto si hay captura
|
|
240
|
+
if (shotPath) {
|
|
241
|
+
slide.addText(`${feature.display || feature.name}`, {
|
|
242
|
+
x: S.marginX, y: 1.05, w: textW, h: 0.6,
|
|
243
|
+
fontSize: 15, bold: true, color: colors.text, fontFace: theme.font,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
147
246
|
}
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
slide.addText('📷 Screenshot: ' + (feature.display || feature.name), [1, 6, 8, 0.5], {
|
|
156
|
-
fontSize: 12, color: colors.textLight, fontFace: 'Inter', align: 'center',
|
|
157
|
-
});
|
|
247
|
+
|
|
248
|
+
if (shotPath) {
|
|
249
|
+
phoneShot(slide, {
|
|
250
|
+
x: S.page.width - S.marginX - 2.6, y: 1.25,
|
|
251
|
+
w: 2.55, h: 4.9, imagePath: shotPath,
|
|
252
|
+
label: `Captura de ${feature.display || feature.name}`,
|
|
253
|
+
}, theme);
|
|
158
254
|
} else {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
255
|
+
phoneShot(slide, {
|
|
256
|
+
x: S.page.width - S.marginX - 2.6, y: 1.25,
|
|
257
|
+
w: 2.55, h: 4.9, imagePath: null,
|
|
258
|
+
label: `Captura de ${feature.display || feature.name} en uso`,
|
|
163
259
|
}, theme);
|
|
164
|
-
slide.clearShapes?.(); // remove placeholder
|
|
165
260
|
}
|
|
166
|
-
|
|
167
261
|
addFooter(slide, theme, slideNum, totalSlides);
|
|
168
|
-
slideNum++;
|
|
169
262
|
}
|
|
170
263
|
|
|
171
|
-
// =====
|
|
172
|
-
if (
|
|
173
|
-
slide =
|
|
174
|
-
slide
|
|
175
|
-
addSectionHeader(slide, 'FLUJOS DE TRABAJO', theme);
|
|
176
|
-
|
|
177
|
-
const steps = appData.hasModals.slice(0, 4).map((modal, i) => ({
|
|
178
|
-
title: `Modal: ${modal}`,
|
|
179
|
-
desc: 'Paso 1 → Paso 2 → Paso 3',
|
|
180
|
-
}));
|
|
181
|
-
|
|
264
|
+
// ===== N+1. Flujos (modales) =====
|
|
265
|
+
if (hasModals) {
|
|
266
|
+
slide = newSlide();
|
|
267
|
+
addSectionHeader(slide, 'FLUJOS DE TRABAJO', theme, ++slideNum, totalSlides);
|
|
182
268
|
workflowSlide(slide, {
|
|
183
|
-
|
|
184
|
-
steps,
|
|
269
|
+
textWidth: S.contentWidth - 0.65,
|
|
270
|
+
steps: appData.hasModals.slice(0, 6).map((m) => ({
|
|
271
|
+
title: m.replace(/([a-z])([A-Z])/g, '$1 $2'),
|
|
272
|
+
desc: 'Flujo de usuario con confirmación y resultado trazable.',
|
|
273
|
+
})),
|
|
185
274
|
}, theme);
|
|
186
275
|
addFooter(slide, theme, slideNum, totalSlides);
|
|
187
|
-
slideNum++;
|
|
188
276
|
}
|
|
189
277
|
|
|
190
|
-
// =====
|
|
191
|
-
slide =
|
|
192
|
-
slide
|
|
193
|
-
addSectionHeader(slide, 'ARQUITECTURA', theme);
|
|
278
|
+
// ===== N+2. Arquitectura =====
|
|
279
|
+
slide = newSlide();
|
|
280
|
+
addSectionHeader(slide, 'ARQUITECTURA', theme, ++slideNum, totalSlides);
|
|
194
281
|
architectureSlide(slide, {
|
|
195
|
-
title: 'Estructura de capas',
|
|
196
282
|
layers: [
|
|
197
|
-
{ name: 'UI', desc: 'Dashboard, Cards, Modals, Overlays', color: colors.accent },
|
|
198
|
-
{ name: 'Core', desc: '
|
|
199
|
-
{ name: 'Config', desc: '
|
|
200
|
-
{ name: '
|
|
283
|
+
{ name: 'UI', desc: 'Dashboard, Cards, Modals, Overlays (src/ui/)', color: colors.accent },
|
|
284
|
+
{ name: 'Core', desc: 'Lógica de negocio y procesamiento (src/core/)', color: colors.info },
|
|
285
|
+
{ name: 'Config', desc: 'Tema, constantes y metadata (skill.json)', color: colors.violet },
|
|
286
|
+
{ name: 'Datos', desc: 'API ERP, catálogo y cache local', color: colors.success },
|
|
201
287
|
],
|
|
202
288
|
}, theme);
|
|
203
289
|
addFooter(slide, theme, slideNum, totalSlides);
|
|
204
|
-
slideNum++;
|
|
205
290
|
|
|
206
|
-
// =====
|
|
207
|
-
slide =
|
|
208
|
-
slide
|
|
209
|
-
addSectionHeader(slide, 'BUENAS PRÁCTICAS', theme);
|
|
291
|
+
// ===== N+3. Buenas prácticas =====
|
|
292
|
+
slide = newSlide();
|
|
293
|
+
addSectionHeader(slide, 'BUENAS PRÁCTICAS', theme, ++slideNum, totalSlides);
|
|
210
294
|
checklistSlide(slide, {
|
|
211
|
-
title: 'Recomendaciones de uso',
|
|
212
295
|
items: [
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
296
|
+
'Mantener la sesión activa solo mientras se usa la app',
|
|
297
|
+
'Actualizar datos antes de decidir (auto-refresh o botón de recarga)',
|
|
298
|
+
'Usar la exportación a Excel para seguimiento y auditoría',
|
|
299
|
+
'Reportar anomalías al administrador con el registro específico',
|
|
300
|
+
'Compartir el dispositivo solo con sesión cerrada',
|
|
301
|
+
'Verificar conectividad si los datos aparecen desactualizados',
|
|
219
302
|
],
|
|
220
303
|
}, theme);
|
|
221
304
|
addFooter(slide, theme, slideNum, totalSlides);
|
|
222
|
-
slideNum++;
|
|
223
305
|
|
|
224
|
-
// =====
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
appData.hasAutoRefresh ? 'Auto-refresh activado' : '',
|
|
236
|
-
appData.hasSearch ? 'Buscador flotante implementado' : '',
|
|
237
|
-
appData.hasExport ? 'Exportacion a Excel disponible' : '',
|
|
238
|
-
appData.hasModals.length > 0 ? `${appData.hasModals.length} modales de trabajo` : '',
|
|
239
|
-
].filter(Boolean);
|
|
240
|
-
|
|
306
|
+
// ===== N+4. Límites (opcional, definidos por el proyecto) =====
|
|
307
|
+
if (hasLimits) {
|
|
308
|
+
slide = newSlide();
|
|
309
|
+
addSectionHeader(slide, 'LÍMITES CONOCIDOS', theme, ++slideNum, totalSlides);
|
|
310
|
+
limitsSlide(slide, { items: appData.limits }, theme);
|
|
311
|
+
addFooter(slide, theme, slideNum, totalSlides);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ===== Resumen =====
|
|
315
|
+
slide = newSlide();
|
|
316
|
+
addSectionHeader(slide, 'RESUMEN', theme, ++slideNum, totalSlides);
|
|
241
317
|
checklistSlide(slide, {
|
|
242
|
-
|
|
243
|
-
items:
|
|
318
|
+
startY: 1.5,
|
|
319
|
+
items: [
|
|
320
|
+
`Nombre: ${appName}`,
|
|
321
|
+
`Versión: ${appData.version || '1.0.0'}`,
|
|
322
|
+
`Framework: ${appData.framework || 'N/A'}`,
|
|
323
|
+
`Módulos UI: ${appData.features.length}`,
|
|
324
|
+
appData.hasAutoRefresh ? 'Auto-refresh de datos activado' : 'Actualización manual de datos',
|
|
325
|
+
appData.hasSearch ? 'Buscador global disponible' : null,
|
|
326
|
+
appData.hasExport ? 'Exportación a Excel disponible' : null,
|
|
327
|
+
hasModals ? `${appData.hasModals.length} flujos con modales de trabajo` : null,
|
|
328
|
+
].filter(Boolean).map((t) => ({ text: t, checked: true })),
|
|
244
329
|
}, theme);
|
|
245
|
-
|
|
330
|
+
slide.addText('Documentación completa: README.md · powered by G360', {
|
|
331
|
+
x: S.marginX, y: 6.6, w: S.contentWidth, h: 0.4,
|
|
332
|
+
fontSize: theme.typo.sizes.bodySmall, color: colors.textMuted,
|
|
333
|
+
align: 'center', fontFace: theme.font,
|
|
334
|
+
});
|
|
246
335
|
|
|
247
|
-
// Save
|
|
248
|
-
const
|
|
336
|
+
// Save — por defecto dentro del repo de la app (cada PPTX es propio de su repo)
|
|
337
|
+
const slug = appName.toLowerCase().replace(/\s+/g, '-');
|
|
338
|
+
const defaultDir = options.targetDir || process.cwd();
|
|
339
|
+
const outPath = options.outPath
|
|
340
|
+
|| path.join(defaultDir, mode === 'demo' ? `${slug}-demo.pptx` : `${slug}-manual.pptx`);
|
|
249
341
|
await pptx.writeFile({ fileName: outPath });
|
|
250
342
|
return outPath;
|
|
251
343
|
}
|
|
252
344
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
return 'feature';
|
|
345
|
+
function buildTagline(appData) {
|
|
346
|
+
const bits = [];
|
|
347
|
+
if (appData.framework) bits.push(appData.framework);
|
|
348
|
+
if (appData.features.length) bits.push(`${appData.features.length} módulos UI`);
|
|
349
|
+
if (appData.hasModals?.length) bits.push(`${appData.hasModals.length} flujos de trabajo`);
|
|
350
|
+
if (appData.type === 'web' && appData.hasPwa) bits.push('PWA instalable');
|
|
351
|
+
return bits.join(' · ');
|
|
261
352
|
}
|
|
262
353
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
354
|
+
function buildTechBullets(appData, features) {
|
|
355
|
+
const bullets = [];
|
|
356
|
+
if (appData.framework) bullets.push(`Framework: ${appData.framework}`);
|
|
357
|
+
bullets.push(`${appData.features.length} módulos UI detectados`);
|
|
358
|
+
if (appData.hasModals?.length) bullets.push(`${appData.hasModals.length} modales de trabajo en src/ui/modals/`);
|
|
359
|
+
if (appData.hasExport) bullets.push('Exportación a Excel integrada');
|
|
360
|
+
if (appData.hasAutoRefresh) bullets.push('Auto-refresh de datos (~15 min)');
|
|
361
|
+
if (appData.hasSupabase) bullets.push('Datos en tiempo real vía Supabase');
|
|
362
|
+
if (appData.hasPwa) bullets.push('PWA instalable con cache offline');
|
|
363
|
+
if (appData.hasCharts) bullets.push('Visualización de datos con gráficos');
|
|
364
|
+
if (appData.hasSearch) bullets.push('Búsqueda global con overlay flotante');
|
|
365
|
+
bullets.push('Metadata y eventos declarados en skill.json');
|
|
366
|
+
return bullets.slice(0, 6);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function buildCapabilityLine(appData) {
|
|
370
|
+
const caps = [];
|
|
371
|
+
if (appData.hasAutoRefresh) caps.push('datos en tiempo real');
|
|
372
|
+
if (appData.hasSearch) caps.push('búsqueda global');
|
|
373
|
+
if (appData.hasExport) caps.push('export a Excel');
|
|
374
|
+
if (appData.hasSource1 || appData.hasSupabase) caps.push('conexión a ERP/Supabase');
|
|
375
|
+
if (appData.hasPwa) caps.push('modo offline (PWA)');
|
|
376
|
+
if (appData.hasCharts) caps.push('gráficos interactivos');
|
|
377
|
+
return caps.length > 0
|
|
378
|
+
? `Capacidades destacadas: ${caps.join(' · ')}.`
|
|
379
|
+
: 'Revisa los módulos en las siguientes páginas para conocer las capacidades de la app.';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function buildWorkflowSteps(appData) {
|
|
383
|
+
if (appData.templates?.length >= 4) {
|
|
384
|
+
return appData.templates.slice(0, 6).map((t, i) => ({
|
|
385
|
+
title: `${i + 1} · ${t.display}`,
|
|
386
|
+
desc: `Interacción "${t.name.replace(/^_on_/, '').replace(/_/g, ' ')}" registrada en src/app.py.`,
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
267
389
|
return [
|
|
268
|
-
{
|
|
269
|
-
{
|
|
270
|
-
{
|
|
271
|
-
{
|
|
390
|
+
{ title: 'Ingresar', desc: 'Abre la app y autentícate si el proyecto lo requiere.' },
|
|
391
|
+
{ title: 'Revisar el dashboard', desc: 'El resumen principal concentra KPIs y accesos a los módulos.' },
|
|
392
|
+
{ title: 'Explorar módulos', desc: 'Cada módulo cubre una parte del flujo operativo del negocio.' },
|
|
393
|
+
{ title: 'Operar', desc: 'Registra, edita o consulta según el módulo de trabajo.' },
|
|
394
|
+
{ title: 'Exportar / compartir', desc: 'Genera reportes en Excel cuando el proyecto lo integre.' },
|
|
395
|
+
{ title: 'Cerrar sesión', desc: 'Si compartes el dispositivo, cierra tu sesión al terminar.' },
|
|
272
396
|
];
|
|
273
397
|
}
|
|
274
|
-
|
|
275
|
-
import path from 'path';
|