
Hace dos años os enseñé en Madrid una app móvil de fichar al lado de PowerBuilder. El año pasado, mi «PowerServer». Este año vuelvo a subirme al escenario, y esta vez la app de fichar va dentro de PowerBuilder y, a la vez, fuera. Y es el mismo código.
Os lo cuento el martes 27 de octubre en Barcelona, en la Appeon PowerBuilder Regional Conference España 2026.
| Cuándo | Martes, 27 de octubre de 2026 |
|---|---|
| Dónde | Hotel NH Sants, Barcelona |
| Organiza | Softpi |
| Mi charla | «Modernizando PowerBuilder con tecnologías web» (30 minutos) |
| Inscripción | softpi.com |
¿De qué va la charla?
La idea cabe en una frase: no se reescribe la aplicación, se le abre una ventana a la web.
Muchos tenemos aplicaciones PowerBuilder con años de negocio dentro. La mía de cada día es un ERP de más de mil ventanas repartidas en más de cien librerías. Reescribirlo en web sería caro, arriesgado y, sobre todo, innecesario. Pero tampoco hace falta quedarse quieto mientras la web va por delante.
La puerta ya la tenemos en casa: el control WebBrowser de PowerBuilder, que funciona sobre WebView2. Con él, PowerBuilder puede pintar HTML, hablar con JavaScript y dejar que JavaScript le hable a él. La charla va subiendo una escalera en la que cada escalón está más pegado a PowerBuilder que el anterior, y se puede parar en cualquiera: cada uno aporta algo por sí solo.
| # | Escalón | Qué demuestra |
|---|---|---|
| 0 | La web decorativa: el fondo del MDI | HTML como pintura. Riesgo cero |
| 1 | La web que muestra: un dashboard con Chart.js | PowerBuilder le pasa datos a la web |
| 2 | La web que sustituye un control: AG Grid comiendo de un DataWindow | Un grid moderno sin tocar el origen de datos |
| 3 | La web que responde: de JavaScript a PowerBuilder | Doble clic en el grid y se abre una ventana nativa |
| 4 | La web que es la aplicación: fichar dentro del ERP | Una app React entera, sin volver a pedir login |
| 5 | La misma app fuera: navegador y móvil | Fichas fuera y aparece dentro |
Vamos escalón por escalón, con capturas de la demo tal y como la veréis en Barcelona.
Y antes de que alguien piense que me estoy destripando la charla: no lo publico como spoiler. Lo publico para que el día 27 podáis seguirla con más facilidad y, sobre todo, para que quien tenga ganas se descargue la demo, la pruebe y me haga llegar sus dudas o los fallos que encuentre. Todo lo que me llegue antes me sirve para que la presentación en Barcelona sea mejor.
Escalón 0: la web decorativa
El primer paso es casi una broma, y precisamente por eso es el mejor para empezar: el fondo de la ventana MDI es un WebBrowser que pinta una página HTML con el logo en marca de agua. El color y la opacidad cambian según el tema de PowerBuilder que esté activo (GetTheme()). Si algo falla aquí, lo peor que pasa es que el fondo sale gris.

Un detalle que parece tonto y no lo es: el logo viaja con la aplicación. Al principio se descargaba de mi web, y el día que me quedé sin wifi el fondo salió vacío. La charla se da con el wifi apagado, así que todo viaja con la demo: el logo, Chart.js, la web y la API. Nada depende de fuera.
Escalón 1: la web que muestra
Un dashboard de ventas con Chart.js. Los datos no salen de ninguna base de datos: PowerBuilder carga las facturas del año desde un fichero JSON, suma los importes por mes y genera la página con los datos ya dentro. Los colores de la gráfica también los decide PowerBuilder a partir de su tema.

Y aquí ya asoma el camino de vuelta: pulsar la tarjeta de «Facturas Emitidas» le pide a PowerBuilder que abra el listado. Pero eso lo dejo para el escalón 3.
Escalón 2: la web que sustituye un control
El listado de facturas es un AG Grid, pero detrás sigue habiendo un DataWindow, invisible, que es quien tiene los datos. PowerBuilder describe sus columnas, exporta las filas a JSON y se las pasa al grid:
// w_con_facturas.wf_grid_cargar_datos ls_cols = wf_grid_columnas_dinamicas() // a partir de Describe() wb_1.EvaluateJavascriptSync("window.setColumns(" + ls_cols + ")") ls_json = dw_1.ExportJson(False) wb_1.EvaluateJavascriptSync("window.loadData(" + ls_json + ")")

El usuario puede ordenar, filtrar, mover, redimensionar y ocultar columnas, y exportar a Excel. Y aquí va una de mis frases favoritas de la charla: la web pinta, pero quien recuerda es la aplicación de escritorio. Al cerrar la ventana, PowerBuilder le pide al grid su configuración (orden, anchos, columnas ocultas) y la guarda en un fichero. Al volver a abrirla, se la devuelve. Sin base de datos, sin cookies y sin servidor.
Escalón 3: la web que responde
Este es el que más sorprende. Hasta ahora PowerBuilder mandaba y la web obedecía. Ahora es al revés: haces doble clic en una factura del grid y se abre el mantenimiento nativo de PowerBuilder, con esa factura cargada.

La línea que lo hace posible es RegisterEvent. Sin ella, JavaScript no puede llamar a PowerBuilder; con ella, una página incrustada pasa a ser parte de la aplicación:
// PowerBuilder, una sola vez al cargar la página wb_1.RegisterEvent("ue_dblclick") // JavaScript, en el doble clic del grid window.webBrowser.asyncfun.prototype.ue_dblclick(String(fila)) // PowerBuilder, evento ue_dblclick(string as_row) del WebBrowser ll_fila = Long(as_row) ls_serie = Trim(dw_1.GetItemString(ll_fila, "serie")) ls_factura = Trim(dw_1.GetItemString(ll_fila, "factura")) OpenSheetWithParm(w_mant_facturas, ls_serie + "|" + ls_factura, w_frame, 0, Layered!)
Fijaos en un detalle: el grid no manda la clave de la factura, manda el número de fila del DataWindow. Quien sabe qué factura es sigue siendo PowerBuilder. La web no sustituye a PowerBuilder: le pasa el trabajo.
Y una lección aprendida en producción: no se controla la web simulando clics. En una aplicación mía se cerraba la ventana simulando un clic sobre un botón de la web. El día que ese botón cambió de clase, el clic falló en silencio y la ventana se quedaba abierta para siempre. Con un evento registrado, eso no pasa.
Escalón 4: la web que es la aplicación
Aquí está el salto de verdad. Pulsáis «Fichar» en el ribbon y se abre una ventana de PowerBuilder que por dentro es una aplicación React completa: calendario, movimientos del día y botones de entrada y salida. Y entra sin pedir login, porque ya estáis dentro del ERP.

Compartir la sesión sin pasar el token por la URL
Lo fácil sería que PowerBuilder hiciera login, recibiera su JWT y se lo inyectara a la web con EvaluateJavascript. Funciona, pero en la demo hago lo correcto: un ticket de un solo uso.
- PowerBuilder hace login contra la API (
RestClient) y recibe su JWT. - Con ese JWT pide un ticket que vale una sola vez y caduca a los 30 segundos.
- Navega el WebBrowser a
/sso?t=<ticket>&embedded=true. - La web canjea el ticket y recibe su propio JWT. El token nunca viaja en la URL ni queda en el historial.
// w_fichar_html.wf_entrar (resumido) lrc_cliente.SendPostRequest(is_url_base + "/api/Auth/Login", ls_body, ls_respuesta) // ... se lee el token de la respuesta con JsonParser lrc_cliente.SetRequestHeader("Authorization", "Bearer " + ls_token) lrc_cliente.SendPostRequest(is_url_base + "/api/Auth/Ticket", "{}", ls_respuesta) // ... se lee el ticket wb_1.Navigate(is_url_base + "/sso?t=" + ls_ticket + "&embedded=true")
En la API (.NET 10), que el ticket valga una sola vez es tan sencillo como sacarlo del diccionario al canjearlo:
public Usuarios? Canjear(string ticket) { Limpiar(); // TryRemove: canjear lo consume. Un ticket vale UNA vez. if (!_tickets.TryRemove(ticket, out var entrada)) return null; if (entrada.Caduca < DateTime.UtcNow) return null; return entrada.Usuario; }
Y el remate: el mismo endpoint de login lo usan los tres clientes, PowerBuilder, el navegador y el móvil. Un solo [Authorize] y tres clientes. Detrás, la API usa el ORM DataWindow de Appeon en el servidor (DWNet.Data / SnapObjects 5.1, que ya soporta .NET 10): el mismo objeto DataWindow, ahora en el backend.
La misma web, un solo build, tres comportamientos
Ese &embedded=true es el detalle que mejor resume la charla. Con él, la misma web se comporta distinto solo porque la abre PowerBuilder:
| Fuera (navegador / móvil) | Dentro del ERP | |
|---|---|---|
| Botón de la derecha | Historial | Cerrar: cierra la ventana de PowerBuilder |
| Historial | Ese botón | Un icono en el pie |
| Aviso al fichar | Lo pinta la web | Lo pinta PowerBuilder, nativo |
| Insignia | WEB / MOVIL | DENTRO DEL ERP |
| Cerrar sesión | Icono en la cabecera | No aparece: aquí se cierra la ventana |
Además, PowerBuilder le pasa a la web con qué tema se está pintando (&tema=Flat Design Dark, por ejemplo) y la web se viste igual. El escritorio y la web embebida dejan de parecer dos aplicaciones distintas.
Para la vuelta, la ventana registra dos eventos: ue_close_window y ue_gf_msgbox. En la web, si el puente existe se usa; si no, la web se apaña sola:
export function avisoNativo(titulo: string, mensaje: string): boolean { if (typeof window.webBrowser?.ue_gf_msgbox !== 'function') return false try { window.webBrowser.ue_gf_msgbox(JSON.stringify({ title: titulo, message: mensaje })) } catch {} return true } // Al fichar: aviso nativo si estamos dentro del ERP; si no, el de la web if (!avisoNativo('Fichaje registrado', texto)) setAviso(texto)
Escalón 5: la misma app, fuera
La misma web, sin recompilar nada, en el navegador:

Y en el móvil, a una columna. Es una PWA: se instala en la pantalla de inicio sin pasar por ninguna tienda y sin APK.

Y aquí va el momento de la demo que más me gusta: fichas la salida en el móvil y, a los pocos segundos, aparece dentro del ERP, en la ventana de PowerBuilder que tenías abierta:

Todo esto lo sirve un solo proceso: la API de .NET 10 sirve también la web compilada. Arrancas un ejecutable y tienes la API, la web y la PWA.
Lo que he aprendido por el camino
Construyendo la demo salieron unos cuantos detalles que no se ven en ningún tutorial. En la charla tendrán su propia sección; aquí os adelanto algunos:
- El WebBrowser se pinta siempre por encima. El fondo del MDI es un WebBrowser, y al abrir una ventana encima la tapaba entera: solo se veía el logo. Hay que esconderlo al abrir una hoja y volver a mostrarlo cuando se cierra la última.
- Cerrar la ventana dentro del propio evento del puente no funciona. El JavaScript llama a PowerBuilder y se queda esperando respuesta; si la ventana se destruye en ese momento, la respuesta no llega nunca y la web recibe un error. La solución es una palabra:
Post Close(Parent). El evento contesta y la ventana se cierra un instante después. EvaluateJavascriptSyncdevuelve el resultado envuelto, del tipo{"type":"string","value":"..."}, y por referencia en el segundo argumento. Si lo guardas tal cual, no falla nada… pero tampoco funciona.ImportJsoncasa las columnas por posición, no por nombre. Un campo de más en el JSON y todo sale corrido.- Compilar no prueba nada. Que el p-code compile solo dice que la sintaxis es correcta. Escribir ficheros, navegar o cargar imágenes solo se ve ejecutando.
Y uno de regalo que no tiene que ver con la web: el editor de PowerBuilder viene de fábrica con Tahoma, que no es de ancho fijo. Cualquier cosa alineada por columnas en un script sale descuadrada. Lo primero que hago ahora en un PowerBuilder recién instalado es cambiarla por Consolas.
Las tecnologías
PowerBuilder 2025, WebView2, .NET 10, DWNet.Data / SnapObjects 5.1, JWT, React 19, Vite, TypeScript, PWA, SQL Server, Chart.js y AG Grid. Nada que no podáis montar mañana en vuestra aplicación.
El código, ya publicado
Tenéis la demo completa publicada en github.com/rasanfe/AppeonSpain2026, con licencia MIT: la aplicación PowerBuilder, la API y la web, con los scripts de la base de datos y un .bat que lo arranca todo. Así, si venís a la charla, no hace falta que toméis apuntes.
Y aquí va la petición: descargadla, probadla y contadme. Si algo no arranca, si un paso no se entiende o si encontráis un fallo, dejádmelo en los Issues del repositorio o en los comentarios de este artículo. Cada duda que me llegue antes del 27 es una duda que resolveré mejor en el escenario.
Eso sí: es una demo. El JWT es de manual y el usuario y el PIN viajan en claro sobre localhost. Sirve para enseñar el mecanismo, no es la seguridad de ninguna aplicación real.
Y si queréis ver de dónde viene todo esto, las charlas anteriores también están publicadas:
- 2025, mi «PowerServer»: rasanfe/PersonDemo03
- 2024, la app de fichar en React Native: rasanfe/FicharDemo y rasanfe/FicharDemoApi
Nos vemos en Barcelona
Si trabajáis con PowerBuilder, merece la pena ir: se aprende mucho de las charlas y todavía más de los pasillos. La inscripción está en la web de Softpi, y el anuncio, en su LinkedIn.
Después del 27 os contaré aquí cómo fue, con las diapositivas y, si hay grabación, el vídeo.
¡Nos vemos en el próximo artículo! Y recuerda: en PowerBuilder, los límites solo están en nuestra imaginación. 🚀
Comentarios
Publicar un comentario