Nos vemos en Barcelona: modernizando PowerBuilder con tecnologías web

Cartel de la Appeon PowerBuilder Regional Conference Spain 2026: Modernizando PowerBuilder con tecnologías web, 27 de octubre, Barcelona

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ándoMartes, 27 de octubre de 2026
DóndeHotel NH Sants, Barcelona
OrganizaSoftpi
Mi charla«Modernizando PowerBuilder con tecnologías web» (30 minutos)
Inscripciónsoftpi.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
0La web decorativa: el fondo del MDIHTML como pintura. Riesgo cero
1La web que muestra: un dashboard con Chart.jsPowerBuilder le pasa datos a la web
2La web que sustituye un control: AG Grid comiendo de un DataWindowUn grid moderno sin tocar el origen de datos
3La web que responde: de JavaScript a PowerBuilderDoble clic en el grid y se abre una ventana nativa
4La web que es la aplicación: fichar dentro del ERPUna app React entera, sin volver a pedir login
5La misma app fuera: navegador y móvilFichas 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.

Ventana MDI de PowerBuilder con el fondo pintado en HTML

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.

Dashboard de ventas con Chart.js dentro de PowerBuilder

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 + ")")
AG Grid alimentado desde un DataWindow dentro de PowerBuilder

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.

Mantenimiento de facturas nativo de PowerBuilder abierto desde el grid web

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.

La web de fichar embebida en una ventana de PowerBuilder, con la insignia 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.

  1. PowerBuilder hace login contra la API (RestClient) y recibe su JWT.
  2. Con ese JWT pide un ticket que vale una sola vez y caduca a los 30 segundos.
  3. Navega el WebBrowser a /sso?t=<ticket>&embedded=true.
  4. 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 derechaHistorialCerrar: cierra la ventana de PowerBuilder
HistorialEse botónUn icono en el pie
Aviso al ficharLo pinta la webLo pinta PowerBuilder, nativo
InsigniaWEB / MOVILDENTRO DEL ERP
Cerrar sesiónIcono en la cabeceraNo 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:

La web de fichar en el navegador, con la insignia WEB y el botón Historial

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.

La web de fichar en el móvil, con la insignia MOVIL

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:

La ventana del ERP mostrando la salida fichada desde el móvil

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.
  • EvaluateJavascriptSync devuelve 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.
  • ImportJson casa 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:

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