xonecode 0.1.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 +167 -0
- package/dist/agent/authEnDisco.js +56 -0
- package/dist/agent/authEnDisco.js.map +1 -0
- package/dist/agent/configEnDisco.js +109 -0
- package/dist/agent/configEnDisco.js.map +1 -0
- package/dist/agent/crearProyecto.js +34 -0
- package/dist/agent/crearProyecto.js.map +1 -0
- package/dist/agent/entorno.js +124 -0
- package/dist/agent/entorno.js.map +1 -0
- package/dist/agent/guionizado.js +53 -0
- package/dist/agent/guionizado.js.map +1 -0
- package/dist/agent/instantanea.js +129 -0
- package/dist/agent/instantanea.js.map +1 -0
- package/dist/agent/interrupts.js +83 -0
- package/dist/agent/interrupts.js.map +1 -0
- package/dist/agent/mensajes.js +24 -0
- package/dist/agent/mensajes.js.map +1 -0
- package/dist/agent/modelos.js +42 -0
- package/dist/agent/modelos.js.map +1 -0
- package/dist/agent/normalizar.js +20 -0
- package/dist/agent/normalizar.js.map +1 -0
- package/dist/agent/perfiles.js +103 -0
- package/dist/agent/perfiles.js.map +1 -0
- package/dist/agent/proyecto.js +107 -0
- package/dist/agent/proyecto.js.map +1 -0
- package/dist/agent/puente.js +142 -0
- package/dist/agent/puente.js.map +1 -0
- package/dist/agent/resumenDeTool.js +48 -0
- package/dist/agent/resumenDeTool.js.map +1 -0
- package/dist/agent/skills.js +65 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/textoDeTool.js +113 -0
- package/dist/agent/textoDeTool.js.map +1 -0
- package/dist/agent/turnoReal.js +173 -0
- package/dist/agent/turnoReal.js.map +1 -0
- package/dist/agent/verificador.js +65 -0
- package/dist/agent/verificador.js.map +1 -0
- package/dist/agent/xoneAgent.js +106 -0
- package/dist/agent/xoneAgent.js.map +1 -0
- package/dist/bin.js +9 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/aprobar.js +82 -0
- package/dist/cli/aprobar.js.map +1 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/consola.js +346 -0
- package/dist/cli/consola.js.map +1 -0
- package/dist/cli/describe.js +53 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/doctor.js +45 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/main.js +520 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/markdown.js +90 -0
- package/dist/cli/markdown.js.map +1 -0
- package/dist/cli/run.js +164 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spinner.js +89 -0
- package/dist/cli/spinner.js.map +1 -0
- package/dist/cli/stdio.js +193 -0
- package/dist/cli/stdio.js.map +1 -0
- package/dist/cli/tema.js +40 -0
- package/dist/cli/tema.js.map +1 -0
- package/dist/cli/verify.js +62 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/core/bitacora.js +30 -0
- package/dist/core/bitacora.js.map +1 -0
- package/dist/core/config.js +236 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/contextos.js +56 -0
- package/dist/core/contextos.js.map +1 -0
- package/dist/core/deps.js +51 -0
- package/dist/core/deps.js.map +1 -0
- package/dist/core/diff.js +79 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/esqueleto.js +366 -0
- package/dist/core/esqueleto.js.map +1 -0
- package/dist/core/events.js +2 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/modelos.js +85 -0
- package/dist/core/modelos.js.map +1 -0
- package/dist/core/notify.js +106 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/ports.js +114 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/turno.js +127 -0
- package/dist/core/turno.js.map +1 -0
- package/dist/vendor/hitl.js +103 -0
- package/dist/vendor/hitl.js.map +1 -0
- package/dist/vendor/skillLoaders/catalog.js +74 -0
- package/dist/vendor/skillLoaders/catalog.js.map +1 -0
- package/dist/vendor/tokenTracking.js +58 -0
- package/dist/vendor/tokenTracking.js.map +1 -0
- package/package.json +41 -0
- package/skills/xone-debugging/SKILL.md +52 -0
- package/skills/xone-debugging/references/faq.md +561 -0
- package/skills/xone-debugging/references/troubleshooting-y-glosario.md +429 -0
- package/skills/xone-development/SKILL.md +105 -0
- package/skills/xone-development/references/anti-patrones.md +94 -0
- package/skills/xone-development/references/css/atributos-por-categoria.md +509 -0
- package/skills/xone-development/references/css/buenas-practicas-y-parser.md +419 -0
- package/skills/xone-development/references/css/dinamicos-cascada-y-componentes.md +550 -0
- package/skills/xone-development/references/css/patrones-material-y-temas.md +1140 -0
- package/skills/xone-development/references/css/propiedades-y-herencia.md +885 -0
- package/skills/xone-development/references/css/selectores-unidades-colores.md +840 -0
- package/skills/xone-development/references/datos/appdata-referencia-ampliada.md +457 -0
- package/skills/xone-development/references/datos/appdata.md +611 -0
- package/skills/xone-development/references/datos/http-sqlmanager-y-crypto.md +720 -0
- package/skills/xone-development/references/datos/http.md +366 -0
- package/skills/xone-development/references/datos/oauth2-y-replica.md +156 -0
- package/skills/xone-development/references/device/biometria-imagedrawing-y-otros.md +544 -0
- package/skills/xone-development/references/device/objetos-de-dispositivo.md +583 -0
- package/skills/xone-development/references/device/systemsettings-referencia-ampliada.md +396 -0
- package/skills/xone-development/references/device/systemsettings-y-permisos.md +342 -0
- package/skills/xone-development/references/fundamentos/conceptos-clave.md +695 -0
- package/skills/xone-development/references/fundamentos/configuracion-app-xml-ini-mappings.md +668 -0
- package/skills/xone-development/references/fundamentos/errores-comunes.md +312 -0
- package/skills/xone-development/references/fundamentos/navegacion-convenciones-y-primer-proyecto.md +576 -0
- package/skills/xone-development/references/fundamentos/plataforma-y-anatomia-de-proyecto.md +387 -0
- package/skills/xone-development/references/indice-completo.md +77 -0
- package/skills/xone-development/references/javascript/coleccion-error-y-usuario.md +260 -0
- package/skills/xone-development/references/javascript/debugging-y-best-practices.md +245 -0
- package/skills/xone-development/references/javascript/metodos-de-los-controles.md +556 -0
- package/skills/xone-development/references/javascript/metodos-nativos-de-la-vista.md +114 -0
- package/skills/xone-development/references/javascript/motor-js-y-contexto-de-ejecucion.md +319 -0
- package/skills/xone-development/references/javascript/objeto-ai-llm-en-dispositivo.md +358 -0
- package/skills/xone-development/references/javascript/objetos-creables-a-m.md +527 -0
- package/skills/xone-development/references/javascript/objetos-creables-n-z.md +385 -0
- package/skills/xone-development/references/javascript/patrones-criticos-seguridad-y-rendimiento.md +595 -0
- package/skills/xone-development/references/javascript/patrones-de-navegacion-datos-y-codigo.md +751 -0
- package/skills/xone-development/references/javascript/patrones-de-ui-voz-integracion-y-seguridad.md +701 -0
- package/skills/xone-development/references/javascript/plantillas-y-funciones-utilitarias.md +647 -0
- package/skills/xone-development/references/javascript/self-y-dataobject.md +449 -0
- package/skills/xone-development/references/javascript/singletons-globales.md +361 -0
- package/skills/xone-development/references/javascript/ui-catalogo-de-metodos.md +464 -0
- package/skills/xone-development/references/javascript/ui-gps-camara-y-multimedia.md +506 -0
- package/skills/xone-development/references/javascript/ui-navegacion-mensajes-y-vista.md +422 -0
- package/skills/xone-development/references/resumen-js-datos-dispositivo.md +80 -0
- package/skills/xone-development/references/resumen-xml-y-css.md +74 -0
- package/skills/xone-development/references/tipos-de-prop.md +28 -0
- package/skills/xone-development/references/xml-ui/asfilter-visibilidad-eventos-y-macros.md +576 -0
- package/skills/xone-development/references/xml-ui/atributos-coll-group-frame.md +235 -0
- package/skills/xone-development/references/xml-ui/atributos-method-macro-script-event-app.md +244 -0
- package/skills/xone-development/references/xml-ui/atributos-prop.md +439 -0
- package/skills/xone-development/references/xml-ui/contents-y-macros.md +392 -0
- package/skills/xone-development/references/xml-ui/errores-comunes-xml.md +245 -0
- package/skills/xone-development/references/xml-ui/estructura-y-nodo-coll.md +326 -0
- package/skills/xone-development/references/xml-ui/eventos-ciclo-de-vida-e-interaccion.md +704 -0
- package/skills/xone-development/references/xml-ui/eventos-sistema-login-y-personalizados.md +944 -0
- package/skills/xone-development/references/xml-ui/layouts-herencia-y-buenas-practicas.md +618 -0
- package/skills/xone-development/references/xml-ui/mapas.md +685 -0
- package/skills/xone-development/references/xml-ui/mappings-y-colecciones-separadas.md +148 -0
- package/skills/xone-development/references/xml-ui/nodos-group-y-frame.md +666 -0
- package/skills/xone-development/references/xml-ui/patrones-de-pantalla.md +643 -0
- package/skills/xone-development/references/xml-ui/prop-atributos-y-condiciones.md +298 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-basicos.md +493 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-combos-y-controles.md +664 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-listas-y-mapas.md +412 -0
- package/skills/xone-plan-builder/SKILL.md +183 -0
- package/skills/xone-plan-builder/agents/openai.yaml +5 -0
- package/skills/xone-plan-builder/references/TASKS-FORMAT.md +132 -0
- package/skills/xone-project-generator/SKILL.md +111 -0
- package/skills/xone-project-generator/references/canonical-sizes.md +292 -0
- package/skills/xone-project-generator/references/colecciones-base.md +16 -0
- package/skills/xone-project-generator/references/convenciones-de-nombres.md +16 -0
- package/skills/xone-project-generator/references/ejemplos-por-sector-y-prohibiciones.md +140 -0
- package/skills/xone-project-generator/references/fase-3-estilos-css.md +597 -0
- package/skills/xone-project-generator/references/fase-6-colecciones.md +762 -0
- package/skills/xone-project-generator/references/fase-7-asfilter-e-integraciones.md +142 -0
- package/skills/xone-project-generator/references/fase-7-entidades-y-estructura-de-pantalla.md +494 -0
- package/skills/xone-project-generator/references/fase-7-plantilla-consola.md +477 -0
- package/skills/xone-project-generator/references/fase-7-plantillas-de-pantalla.md +302 -0
- package/skills/xone-project-generator/references/fase-7-viewmodes-graficos-y-listas.md +681 -0
- package/skills/xone-project-generator/references/fase-7-viewmodes-mapa-y-calendario.md +479 -0
- package/skills/xone-project-generator/references/fases-0-2-analisis-y-modelo-de-datos.md +350 -0
- package/skills/xone-project-generator/references/fases-10-12-readmes-y-validacion.md +194 -0
- package/skills/xone-project-generator/references/fases-4-5-estructura-y-configuracion.md +587 -0
- package/skills/xone-project-generator/references/fases-8-9-eventos-y-javascript.md +861 -0
- package/skills/xone-project-generator/references/flujo-de-generacion.md +67 -0
- package/skills/xone-project-generator/references/plantillas-estandar.md +98 -0
- package/skills/xone-project-generator/references/tamanos-canonicos.md +49 -0
- package/skills/xone-review/SKILL.md +189 -0
- package/skills/xone-spec-builder/SKILL.md +235 -0
- package/skills/xone-spec-builder/agents/openai.yaml +5 -0
- package/skills/xone-spec-builder/references/ADR-FORMAT.md +55 -0
- package/skills/xone-spec-builder/references/CONTEXT-FORMAT.md +42 -0
- package/skills/xone-spec-builder/references/PLAN-FORMAT.md +120 -0
package/skills/xone-development/references/javascript/patrones-criticos-seguridad-y-rendimiento.md
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
# XOne JavaScript — Patrones críticos, seguridad y rendimiento
|
|
2
|
+
|
|
3
|
+
> Fuente: `xone/v2/xone-help-docs/topics/03e-js-patrones-buenas-practicas.md` §9–§11. Referencia de la skill; el índice está en [../../SKILL.md](../../SKILL.md).
|
|
4
|
+
|
|
5
|
+
Contenido: §9 patrones críticos (lock/unlock, startBrowse/endBrowse, filter/restore, preservación de contexto en callbacks asíncronos) · §10 seguridad (prevención de SQL injection, validación de entrada, encriptación, manejo de credenciales) · §11 optimización y rendimiento
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 9. Patrones Críticos de Código
|
|
10
|
+
|
|
11
|
+
Patrones fundamentales que todo desarrollador XOne debe dominar. El uso incorrecto de estos patrones es la causa principal de errores y memory leaks.
|
|
12
|
+
|
|
13
|
+
### 9.1 Patron lock/unlock (Modificación de Colecciones)
|
|
14
|
+
|
|
15
|
+
**Siempre** usar `finally` para garantizar que se ejecute `lock()`, incluso si hay error:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
function agregarRegistro(nombreColl, datos) {
|
|
19
|
+
var coll = appData.getCollection(nombreColl);
|
|
20
|
+
coll.unlock();
|
|
21
|
+
try {
|
|
22
|
+
var obj = coll.createObject();
|
|
23
|
+
for (var key in datos) {
|
|
24
|
+
if (datos.hasOwnProperty(key)) {
|
|
25
|
+
obj[key] = datos[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
coll.addItem(obj);
|
|
29
|
+
obj.save();
|
|
30
|
+
return true;
|
|
31
|
+
} catch(error) {
|
|
32
|
+
ui.showToast("Error: " + error);
|
|
33
|
+
return false;
|
|
34
|
+
} finally {
|
|
35
|
+
coll.lock(); // SIEMPRE se ejecuta
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 9.2 Patron startBrowse/endBrowse (Navegación de Colecciones)
|
|
41
|
+
|
|
42
|
+
**Siempre** usar `finally` para garantizar que se ejecute `endBrowse()`:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
function procesarColeccion(nombreColl) {
|
|
46
|
+
var coll = appData.getCollection(nombreColl);
|
|
47
|
+
coll.startBrowse();
|
|
48
|
+
try {
|
|
49
|
+
coll.moveFirst();
|
|
50
|
+
while (coll.getCurrentItem() != null) {
|
|
51
|
+
var obj = coll.getCurrentItem();
|
|
52
|
+
console.log(obj.MAP_NOMBRE);
|
|
53
|
+
coll.moveNext();
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
coll.endBrowse(); // SIEMPRE se ejecuta
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 9.3 Patron filter/restore (Filtrado Seguro)
|
|
62
|
+
|
|
63
|
+
**Siempre** guardar el filtro original y restaurarlo después de usar, idealmente en `finally`:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
function procesarRegistrosActivos(nombreColl) {
|
|
67
|
+
var coll = appData.getCollection(nombreColl);
|
|
68
|
+
var filtroOriginal = coll.getFilter();
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
coll.setFilter("ACTIVO = 1 AND ESTADO = 'PENDIENTE'");
|
|
72
|
+
coll.loadAll();
|
|
73
|
+
|
|
74
|
+
var count = coll.count();
|
|
75
|
+
for (var i = 0; i < count; i++) {
|
|
76
|
+
var obj = coll.get(i);
|
|
77
|
+
// Procesar...
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
coll.clear();
|
|
81
|
+
} finally {
|
|
82
|
+
coll.setFilter(filtroOriginal); // Restaurar SIEMPRE
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 9.4 Patron de Preservacion de Contexto en Callbacks Asíncronos
|
|
88
|
+
|
|
89
|
+
En callbacks asíncronos de `$http`, `executeActionAfterDelay` y otros, el objeto `self` **puede cambiar de contexto**. Se debe guardar la referencia **antes** de la llamada:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
function cargarDatosServidor(url) {
|
|
93
|
+
var contexto = self; // GUARDAR referencia antes del callback
|
|
94
|
+
|
|
95
|
+
$http.get(url,
|
|
96
|
+
function(sData) {
|
|
97
|
+
// INCORRECTO: self.MAP_DATO = sData;
|
|
98
|
+
contexto.MAP_DATO = sData;
|
|
99
|
+
ui.refresh("MAP_DATO");
|
|
100
|
+
},
|
|
101
|
+
function(nError, sDesc) {
|
|
102
|
+
ui.showToast("Error: " + sDesc);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 9.5 Patron WaitDialog Seguro
|
|
109
|
+
|
|
110
|
+
**Siempre** ocultar el WaitDialog en un bloque `finally`:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
function operacionLarga() {
|
|
114
|
+
ui.showWaitDialog("Procesando...");
|
|
115
|
+
try {
|
|
116
|
+
var coll = appData.getCollection("Datos");
|
|
117
|
+
coll.loadAll();
|
|
118
|
+
// ...procesar...
|
|
119
|
+
} catch(ex) {
|
|
120
|
+
ui.showToast("Error: " + ex);
|
|
121
|
+
} finally {
|
|
122
|
+
ui.hideWaitDialog();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 9.6 Patron Cursor SQL Seguro
|
|
128
|
+
|
|
129
|
+
**Siempre** cerrar el cursor y la conexión en bloques `finally`:
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
function consultarDatos(query, parametros) {
|
|
133
|
+
var sqlManager = new SqlManager();
|
|
134
|
+
try {
|
|
135
|
+
sqlManager.openDatabase({
|
|
136
|
+
databasePath: "gestion.db",
|
|
137
|
+
useExistingConnection: true
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
var cursor = sqlManager.doRawQuery(query, parametros);
|
|
141
|
+
try {
|
|
142
|
+
if (cursor.getCount() > 0) {
|
|
143
|
+
cursor.moveToFirst();
|
|
144
|
+
return {
|
|
145
|
+
id: cursor.getInteger("ID"),
|
|
146
|
+
nombre: cursor.getString("NOMBRE")
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
} finally {
|
|
151
|
+
cursor.close();
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
sqlManager.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 10. Seguridad en JavaScript XOne
|
|
162
|
+
|
|
163
|
+
### 10.1 Prevencion de SQL Injection
|
|
164
|
+
|
|
165
|
+
**Código VULNERABLE (NUNCA hacer esto):**
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// PELIGROSO: concatenacion directa de input del usuario
|
|
169
|
+
function buscarUsuario(loginUsuario) {
|
|
170
|
+
let coll = appData.getCollection("Usuarios");
|
|
171
|
+
// Si loginUsuario = "' OR 1=1 --" obtendria todos los registros
|
|
172
|
+
let usuario = coll.findObject("LOGIN = '" + loginUsuario + "'");
|
|
173
|
+
return usuario;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// PELIGROSO: SQL directo sin parametrizar
|
|
177
|
+
function eliminarRegistro(id) {
|
|
178
|
+
appData.executeSql("DELETE FROM gen_productos WHERE ID = " + id);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Código SEGURO (hacer SIEMPRE esto):**
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// SEGURO: SqlManager con parametros (consultas parametrizadas)
|
|
186
|
+
function buscarUsuarioSeguro(loginUsuario) {
|
|
187
|
+
let sqlManager = new SqlManager();
|
|
188
|
+
try {
|
|
189
|
+
sqlManager.openDatabase({
|
|
190
|
+
databasePath: "gestion.db",
|
|
191
|
+
useExistingConnection: true
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let cursor = sqlManager.doRawQuery(
|
|
195
|
+
"SELECT * FROM gen_usuarios WHERE LOGIN=?",
|
|
196
|
+
loginUsuario // El parametro se escapa automaticamente
|
|
197
|
+
);
|
|
198
|
+
try {
|
|
199
|
+
if (cursor.getCount() > 0) {
|
|
200
|
+
cursor.moveToFirst();
|
|
201
|
+
return {
|
|
202
|
+
id : cursor.getInteger("ID"),
|
|
203
|
+
nombre: cursor.getString("NOMBRE"),
|
|
204
|
+
login : cursor.getString("LOGIN")
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
} finally {
|
|
209
|
+
cursor.close();
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
sqlManager.close();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// SEGURO: Escapar comillas simples para findObject
|
|
217
|
+
function buscarObjetoSeguro(nombreColl, campo, valor) {
|
|
218
|
+
let valorEscapado = cstr(valor).replace(/'/g, "''");
|
|
219
|
+
let coll = appData.getCollection(nombreColl);
|
|
220
|
+
return coll.findObject(campo + "='" + valorEscapado + "'");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// SEGURO: Validar que sea numerico antes de concatenar
|
|
224
|
+
function eliminarRegistroSeguro(id) {
|
|
225
|
+
let nId = parseInt(id);
|
|
226
|
+
if (isNaN(nId) || nId <= 0) {
|
|
227
|
+
ui.showToast("ID no valido");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
appData.executeSql("DELETE FROM gen_productos WHERE ID = " + nId);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 10.2 Validación de Entrada
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
// === Funciones de validación esenciales ===
|
|
238
|
+
|
|
239
|
+
function isEmpty(val) {
|
|
240
|
+
return val === undefined || val === null || val === "";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function cstr(val) {
|
|
244
|
+
if (val === undefined || val === null) return "";
|
|
245
|
+
return val.toString();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function cnum(val) {
|
|
249
|
+
if (val === undefined || val === null || val === "") return 0;
|
|
250
|
+
let num = parseFloat(val);
|
|
251
|
+
return isNaN(num) ? 0 : num;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// === Sanitizacion ===
|
|
255
|
+
function sanearEntrada(valor, maxLength) {
|
|
256
|
+
if (isEmpty(valor)) return "";
|
|
257
|
+
let sValor = cstr(valor).trim();
|
|
258
|
+
if (maxLength && sValor.length > maxLength) {
|
|
259
|
+
sValor = sValor.substring(0, maxLength);
|
|
260
|
+
}
|
|
261
|
+
return sValor;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function validarEmail(email) {
|
|
265
|
+
if (isEmpty(email)) return false;
|
|
266
|
+
return email.indexOf("@") > 0 && email.indexOf(".") > 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function validarTelefono(telefono) {
|
|
270
|
+
if (isEmpty(telefono)) return false;
|
|
271
|
+
let limpio = telefono.replace(/[^\d+]/g, '');
|
|
272
|
+
return limpio.length >= 9;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function validarRango(valor, min, max) {
|
|
276
|
+
let num = cnum(valor);
|
|
277
|
+
return num >= min && num <= max;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// === Patron completo antes de guardar ===
|
|
281
|
+
function validarFormulario() {
|
|
282
|
+
if (isEmpty(self.MAP_NOMBRE)) {
|
|
283
|
+
ui.showToast("El nombre es obligatorio");
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
if (cstr(self.MAP_NOMBRE).length > 100) {
|
|
287
|
+
ui.showToast("El nombre no puede superar 100 caracteres");
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (cnum(self.MAP_CANTIDAD) <= 0) {
|
|
291
|
+
ui.showToast("La cantidad debe ser mayor a 0");
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (!validarEmail(self.MAP_EMAIL)) {
|
|
295
|
+
ui.showToast("Email no valido");
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 10.3 Encriptación de Datos Sensibles
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
// === Encriptacion básica del framework ===
|
|
306
|
+
let encrypted = appData.encryptString("dato sensible");
|
|
307
|
+
let decrypted = appData.decryptString(encrypted);
|
|
308
|
+
|
|
309
|
+
// === API Crypto avanzada ===
|
|
310
|
+
|
|
311
|
+
// Hashing (unidireccional, para passwords)
|
|
312
|
+
function hashPassword(password) {
|
|
313
|
+
return crypto.sha256({
|
|
314
|
+
data : password,
|
|
315
|
+
outputFormat: "hex"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Cifrado simetrico AES
|
|
320
|
+
function cifrarDato(texto) {
|
|
321
|
+
let aesKey = crypto.generateAesKey({
|
|
322
|
+
alias : "app_datos_key",
|
|
323
|
+
keySize : 256,
|
|
324
|
+
useSecureHardware: true,
|
|
325
|
+
useStrongBox : true
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return crypto.encrypt({
|
|
329
|
+
data : texto,
|
|
330
|
+
dataFormat : "string",
|
|
331
|
+
algorithm : "AES/GCM/NoPadding",
|
|
332
|
+
key : aesKey,
|
|
333
|
+
outputFormat: "base64"
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function descifrarDato(textoCifrado) {
|
|
338
|
+
let aesKey = crypto.generateAesKey({
|
|
339
|
+
alias : "app_datos_key",
|
|
340
|
+
keySize : 256,
|
|
341
|
+
useSecureHardware: true
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return crypto.decrypt({
|
|
345
|
+
data : textoCifrado,
|
|
346
|
+
dataFormat : "base64",
|
|
347
|
+
algorithm : "AES/GCM/NoPadding",
|
|
348
|
+
key : aesKey,
|
|
349
|
+
outputFormat: "string"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Cifrar/descifrar archivos
|
|
354
|
+
crypto.encrypt({
|
|
355
|
+
data: "documento.pdf", dataFormat: "file",
|
|
356
|
+
algorithm: "AES/GCM/NoPadding", key: aesKey,
|
|
357
|
+
outputFormat: "file", output: "documento.pdf.enc"
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Firma digital con clave pública/privada
|
|
361
|
+
let keyPair = crypto.generateKeyPair({
|
|
362
|
+
alias: "firma_app", algorithm: "EC", keySize: 384,
|
|
363
|
+
output: "key", outputFormat: "file", useSecureHardware: true
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
let signature = crypto.sign({
|
|
367
|
+
data: "datos a firmar", algorithm: "SHA256withECDSA",
|
|
368
|
+
privateKey: keyPair.getPrivateKey().toPem(), outputFormat: "base64"
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Encoding
|
|
372
|
+
let base64 = crypto.toBase64({ data: "texto", urlSafe: true });
|
|
373
|
+
let decoded = crypto.fromBase64({ data: base64 });
|
|
374
|
+
|
|
375
|
+
// Checksum
|
|
376
|
+
let crc32 = crypto.getChecksum({ type: "crc32", data: "texto" });
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 10.4 Manejo Seguro de Credenciales
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
// INCORRECTO: hardcodear passwords / loguear datos sensibles
|
|
383
|
+
let password = "admin123"; // NUNCA
|
|
384
|
+
console.log("Password: " + password); // NUNCA
|
|
385
|
+
|
|
386
|
+
// CORRECTO: macros globales encriptadas
|
|
387
|
+
function guardarTokenSesion(token) {
|
|
388
|
+
let tokenCifrado = appData.encryptString(token);
|
|
389
|
+
appData.setGlobalMacro("##SESSION_TOKEN##", tokenCifrado);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function obtenerTokenSesion() {
|
|
393
|
+
let tokenCifrado = appData.getGlobalMacro("##SESSION_TOKEN##");
|
|
394
|
+
if (isEmpty(tokenCifrado)) return null;
|
|
395
|
+
return appData.decryptString(tokenCifrado);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// CORRECTO: limpiar credenciales al cerrar sesion
|
|
399
|
+
function cerrarSesion() {
|
|
400
|
+
appData.setGlobalMacro("##SESSION_TOKEN##", "");
|
|
401
|
+
appData.setGlobalMacro("##USERID##", "");
|
|
402
|
+
appData.setGlobalMacro("##USERNAME##", "");
|
|
403
|
+
appData.setGlobalMacro("##USERROLE##", "");
|
|
404
|
+
appData.logout();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// CORRECTO: comunicaciones seguras
|
|
408
|
+
function crearRequestSeguro(token) {
|
|
409
|
+
return {
|
|
410
|
+
headers: {
|
|
411
|
+
"Authorization": "Bearer " + token,
|
|
412
|
+
"Content-Type" : "application/json"
|
|
413
|
+
},
|
|
414
|
+
parameters: {
|
|
415
|
+
connectTimeout : 30000,
|
|
416
|
+
readTimeout : 30000,
|
|
417
|
+
allowUnsafeCertificates: false, // NUNCA true en produccion
|
|
418
|
+
enablePinning : true
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## 11. Optimización y Rendimiento
|
|
427
|
+
|
|
428
|
+
### 11.1 Minimizar Refreshes
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
// INCORRECTO: multiples refresh individuales
|
|
432
|
+
self.MAP_NOMBRE = "Juan";
|
|
433
|
+
ui.refresh("MAP_NOMBRE");
|
|
434
|
+
self.MAP_ESTADO = "Activo";
|
|
435
|
+
ui.refresh("MAP_ESTADO");
|
|
436
|
+
|
|
437
|
+
// CORRECTO: un solo refresh con multiples campos
|
|
438
|
+
self.MAP_NOMBRE = "Juan";
|
|
439
|
+
self.MAP_ESTADO = "Activo";
|
|
440
|
+
ui.refresh("MAP_NOMBRE,MAP_ESTADO");
|
|
441
|
+
|
|
442
|
+
// Solo actualizar el valor sin reconstruir la vista
|
|
443
|
+
ui.refreshValue("MAP_CAMPO");
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 11.2 Gestion de Colecciones
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
// Usar lock/unlock para evitar recargas innecesarias
|
|
450
|
+
function modificarContentEficiente(contentName, datos) {
|
|
451
|
+
let content = self.getContents(contentName);
|
|
452
|
+
content.unlock();
|
|
453
|
+
try {
|
|
454
|
+
let obj = content.createObject();
|
|
455
|
+
for (let key in datos) obj[key] = datos[key];
|
|
456
|
+
content.addItem(obj);
|
|
457
|
+
} finally {
|
|
458
|
+
content.lock();
|
|
459
|
+
}
|
|
460
|
+
content.saveAll();
|
|
461
|
+
ui.refresh(contentName);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// findObject en lugar de loadAll cuando solo se busca uno
|
|
465
|
+
// INCORRECTO (carga TODOS los registros):
|
|
466
|
+
let coll = appData.getCollection("Productos");
|
|
467
|
+
coll.loadAll();
|
|
468
|
+
for (let i = 0; i < coll.getCount(); i++) {
|
|
469
|
+
if (coll.get(i).CODIGO == "PROD001") { break; }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// CORRECTO (busca directamente en BD):
|
|
473
|
+
let coll = appData.getCollection("Productos");
|
|
474
|
+
let producto = coll.findObject("CODIGO = 'PROD001'");
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 11.3 Evitar Bucles Costosos
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
// INCORRECTO: saveAll dentro de un bucle
|
|
481
|
+
for (let i = 0; i < items.length; i++) {
|
|
482
|
+
let obj = coll.createObject();
|
|
483
|
+
obj.MAP_NOMBRE = items[i].nombre;
|
|
484
|
+
coll.addItem(obj);
|
|
485
|
+
obj.save(); // Escribe en BD en cada iteracion
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// CORRECTO: un solo saveAll al final
|
|
489
|
+
coll.unlock();
|
|
490
|
+
for (let i = 0; i < items.length; i++) {
|
|
491
|
+
let obj = coll.createObject();
|
|
492
|
+
obj.MAP_NOMBRE = items[i].nombre;
|
|
493
|
+
coll.addItem(obj);
|
|
494
|
+
}
|
|
495
|
+
coll.lock();
|
|
496
|
+
coll.saveAll();
|
|
497
|
+
|
|
498
|
+
// CORRECTO: para inserciones masivas, usar batch SQL
|
|
499
|
+
let sqlManager = new SqlManager();
|
|
500
|
+
try {
|
|
501
|
+
sqlManager.openDatabase({ databasePath: "gestion.db", useWal: true, useExistingConnection: true });
|
|
502
|
+
let sqls = [];
|
|
503
|
+
for (let i = 0; i < items.length; i++) {
|
|
504
|
+
sqls.push("INSERT INTO gen_tabla (NOMBRE) VALUES ('" +
|
|
505
|
+
items[i].nombre.replace(/'/g, "''") + "')");
|
|
506
|
+
}
|
|
507
|
+
sqlManager.doBatchParseSqls(sqls);
|
|
508
|
+
} finally {
|
|
509
|
+
sqlManager.close();
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 11.4 Uso Eficiente de Contents
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
// Filtrar para limitar la carga
|
|
517
|
+
let content = self.getContents("@Lineas");
|
|
518
|
+
content.setFilter("ACTIVO = 1");
|
|
519
|
+
content.unlock();
|
|
520
|
+
content.clear();
|
|
521
|
+
content.loadAll();
|
|
522
|
+
content.lock();
|
|
523
|
+
|
|
524
|
+
// Limitar registros para listas grandes
|
|
525
|
+
function cargarUltimosRegistros(nombreColl, limite) {
|
|
526
|
+
limite = limite || 50;
|
|
527
|
+
let coll = appData.getCollection(nombreColl);
|
|
528
|
+
coll.clear();
|
|
529
|
+
coll.loadAll();
|
|
530
|
+
coll.doSort("FECHA DESC");
|
|
531
|
+
|
|
532
|
+
let items = [];
|
|
533
|
+
let count = Math.min(coll.getCount(), limite);
|
|
534
|
+
for (let i = 0; i < count; i++) {
|
|
535
|
+
items.push(coll.get(i));
|
|
536
|
+
}
|
|
537
|
+
return items;
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### 11.5 Promesas para Operaciones Asíncronas
|
|
542
|
+
|
|
543
|
+
El motor XOne tiene una implementación custom de `Promise` compatible con ES2024 (`.then`, `.catch`, `.finally`, `Promise.all/allSettled/race/any/withResolvers`). Soporta ramificación (varias `.then` sobre la misma promesa producen cadenas independientes).
|
|
544
|
+
|
|
545
|
+
```javascript
|
|
546
|
+
function startUpdateGpsLoop() {
|
|
547
|
+
new Promise((resolve, reject) => {
|
|
548
|
+
let ventana = ui.getView(self);
|
|
549
|
+
while (!bBreakUpdateGpsLoop) {
|
|
550
|
+
if (!ventana) { reject(new Error("Sin ventana")); return; }
|
|
551
|
+
try {
|
|
552
|
+
actualizarGps();
|
|
553
|
+
} catch (error) {
|
|
554
|
+
reject(error);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
ventana.refreshValue("MAP_LONGITUD", "MAP_LATITUD");
|
|
558
|
+
}
|
|
559
|
+
resolve();
|
|
560
|
+
})
|
|
561
|
+
.then(() => ui.showToast("GPS loop terminado OK"))
|
|
562
|
+
.catch(err => ui.showToast("Error en GPS loop: " + err.message))
|
|
563
|
+
.finally(() => console.log("cleanup"));
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Patrón con `Promise.all` (paralelo, fast-fail):**
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
Promise.all([cargarUsuario(), cargarConfig(), cargarPermisos()])
|
|
571
|
+
.then(([usuario, config, permisos]) => {
|
|
572
|
+
pintarPantalla(usuario, config, permisos);
|
|
573
|
+
})
|
|
574
|
+
.catch(err => ui.msgBox("Error cargando datos: " + err));
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**Patrón con `Promise.allSettled` (paralelo, espera todas):**
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
Promise.allSettled([p1, p2, p3]).then(results => {
|
|
581
|
+
results.forEach((r, i) => {
|
|
582
|
+
if (r.status === "fulfilled") console.log("OK p" + i + ":", r.value);
|
|
583
|
+
else console.log("FAIL p" + i + ":", r.reason);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Patrón con `Promise.withResolvers` (ES2024, evita la indirección del constructor):**
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
592
|
+
// resolve/reject pueden invocarse desde cualquier callback posterior
|
|
593
|
+
ui.startGps(coords => resolve(coords));
|
|
594
|
+
return promise;
|
|
595
|
+
```
|