Praxsuite

Eventos de tabla en el Event Bus

Vincent Depassier · 29 de septiembre de 2026

Un topic puede nombrar tablas como sus fuentes. Desde ese momento, toda escritura confirmada sobre una de esas tablas se anuncia en ese topic: una grilla se redibuja, un tablero se actualiza, una automatización corre, y nadie escribió un bucle de polling.

La declaración vive en el topic, no en la tabla. Una tabla no tiene por qué saber que existe un transporte, y dejarlo del lado del bus pone cada binding en la única pantalla que administra el canal.

Dónde cae el anuncio

Cada cambio se publica dos veces, en dos direcciones:

{topic}:{tableId}     una tabla
{topic}:*             todas las tablas que el topic liga

Únete a la primera cuando te importa una sola tabla y no quieres escuchar nada más. Únete a la segunda cuando las quieres todas: sin ella tendrías que conocer cada id de tabla de antemano y sostener un bus por tabla, lo que además te quema el cupo de 20 buses.

El evento

El nombre del evento es la operación:

Evento

Lo levanta

row.created

una fila insertada, una o muchas

row.updated

una celda, una fila o una actualización masiva

row.deleted

una fila borrada, una, muchas o por filtro

row.reordered

una fila movida a otra posición

Y el payload:

{
  "workspace": "…",
  "table": "…",
  "op": "row.created",
  "rowIds": ["…", "…"],
  "count": 2,
  "at": "2026-09-29T14:03:11.284Z"
}

Tres cosas que vale conocer:

No hay valores de columna, a propósito. El bus no sabe qué tiene permitido ver quien recibe el evento, y una difusión que filtrara una columna fuera de sus reglas de acceso sería un problema mucho peor que una lectura extra. Toma los ids y trae lo que te corresponde.

`rowIds` puede venir vacío. Un borrado por filtro y una importación masiva nunca materializan los ids que tocaron, así que anuncian con la lista vacía y count en cero. Léelo como "esta tabla cambió, vuelve a consultar" y no como "no pasó nada".

`row.reordered` trae un solo id: la fila que la persona movió. Un reordenamiento corre la posición de todas las filas entre el lugar viejo y el nuevo, así que llamarlo row.updated con un id diría algo falso sobre el resto. Si dibujas una lista ordenada, vuelve a consultar el orden.

Qué escrituras anuncian

Todas. Una escritura por la API de consultas, el envío de un formulario, el portal, el nodo de insert o de update de una automatización, una importación masiva, un borrado: cada una anuncia al confirmarse, y cada una confirma antes de anunciar. Un cambio que se revirtió nunca se publica.

Vale decirlo porque antes no era así: por un tiempo solo anunciaban las escrituras que llegaban por la API de consultas, y un topic ligado a una tabla quedaba mudo para automatizaciones, formularios y el portal, sin nada en los registros que lo dijera. Si armaste un rodeo para eso, ya lo puedes sacar.

La ventana de reconexión

El bus no guarda nada, salvo acá, y poco.

Un topic que liga tablas fuente mantiene una ventana chica de replay: los últimos 50 eventos, por 5 minutos, por bus. Entra con el cursor que viste por última vez y recibes lo que te perdiste:

JoinBusSince("orders:*", null, ultimoCursor)  ->  { ok, peers[], missed[] }

El cursor tiene su propia llamada. JoinBus(busKey, ticket) no cambió y es la que usas mientras no tengas cursor; JoinBusSince es el mismo ingreso con la ventana encima. Existen por separado porque el transporte reconoce una llamada por su cantidad de argumentos, así que un cursor agregado al propio JoinBus dejaría afuera a todo cliente construido antes — ver El Event Bus.

Cada publicación te entrega el cursor para guardar:

Publish(...)  ->  { ok, recipients, cursor }

y cada entrada de missed[] trae su propio cursor junto al evento, así puedes guardar el último y retomar desde ahí.

Esto existe porque "nadie estaba escuchando, así que no pasó nada" es la respuesta correcta para un cursor y la equivocada para una conexión caída. Un cliente que miraba una tabla durante un despliegue no eligió perderse esas filas: estuvo desconectado ocho segundos.

Sé preciso sobre lo que no es:

  • No es at-least-once. Un cliente que quedó más atrás que la ventana recibe todo lo que todavía se guarda, para poder distinguir "no me perdí nada" de "me quedé atrás", pero nada repone lo que la ventana ya soltó.

  • No es historial. Está topeado y expira solo. No se puede leer como auditoría.

  • No es una cola. Sin acuses, sin reintentos. Un cliente que lee y después se cae vuelve a leer lo mismo en su próximo ingreso, que es justamente para lo que sirve el cursor en cada entrada.

Lo que tenga que sobrevivir cinco minutos de corte sigue yendo a una tabla.

Solo los topics que ligan tablas fuente mantienen ventana. Un topic sin ellas es tráfico entre peers — cursores, escritura, presencia — donde un evento queda viejo apenas llega el siguiente, y guardarlo costaría una escritura por movimiento de mouse para reponer una posición que nadie quiere.

Reaccionar desde una automatización

Un evento del bus puede iniciar una automatización. Agrega un disparador de tipo BusEvent y dile qué bus y qué evento escuchar:

{ "bus": "orders:*", "event": "row.created" }
  • bus es obligatorio. Un * al final calza con toda instancia de un topic, y * solo calza con todo bus del workspace.

  • event es opcional. Déjalo fuera para escuchar todo en ese bus.

  • Los dos se comparan sin distinguir mayúsculas.

`bus` es obligatorio por una razón. Un disparador sin bus calzaría con el tráfico de cursores y presencia para el que se construyó el bus, y convertiría un disparador mal guardado en una ejecución por tecla. Si de verdad quieres todo, pídelo con "bus": "*".

Cuidado con los ciclos. Un disparador BusEvent cuya automatización escribe de vuelta en una tabla que el mismo topic liga es un bucle: la escritura anuncia, el anuncio corre la automatización, la automatización escribe otra vez. Nada en la configuración puede descartarlo, porque el binding y la automatización se arman por separado. La plataforma topea cuántas veces se puede encolar un disparador por minuto y lo registra fuerte cuando el tope actúa — así el bucle queda visible y acotado en vez de solamente caro — pero el tope es un cinturón de seguridad, no un arreglo. Corta el ciclo, o achica el filtro del disparador.

Un disparador BusEvent todavía no se puede crear desde el portal. El selector de tipo de disparador no lo lista. Créalo por la API o por una herramienta de conector mientras tanto.

Siguiente

  • El Event Bus

  • Automatizaciones: nodos de disparo

  • Filas y columnas de una tabla

  • Auditoría de tablas