Estados de asiento personalizados (Renderer SDK)

Los estados de asiento personalizados te permiten marcar visualmente asientos individuales con un estado arbitrario, definido por la aplicación – por ejemplo reserved, held, vip o sold – sobre lo que el renderer ya esté dibujando. Un estado es una clave de texto: asignas esa clave a un conjunto de asientos en tiempo de ejecución con setSeatsState, y describes cómo debe verse la clave en el theme.seatStyles del renderer.

Esta guía cubre el Renderer SDK del lado del cliente (@seatmap.pro/renderer). Explica cómo aplicar y visualizar estados desde el navegador. Si buscas el ciclo de vida del asiento en el backend y los tipos de hold definidos por el cliente (el modelo de datos state / holdType en el booking service), consulta en su lugar Estados de asiento y tipos de hold – ambos son complementarios.

Visión general

La funcionalidad está construida a partir de dos capas independientes. Mantenerlas separadas en tu mente es la clave para usar la API correctamente.

Capa Qué controla Cómo la manejas
Motor de asignación Qué asientos están en qué estado setSeatsState(seats, key) / clearSeatsState(seats)
Configuración de estilos Cómo se ve una clave de estado theme.seatStyles (declarativo, fijado en la construcción)

El motor de asignación mantiene un mapeo de ID de asiento a una única clave de estado (como máximo una clave por asiento). No sabe nada sobre colores, tamaños ni el DOM. La configuración de estilos mapea cada clave de estado a una descripción de estilo y vive en los ajustes del renderer que pasas en el momento de la construcción; nunca es mutada en tiempo de ejecución por los métodos de estado.

Un asiento produce una superposición visible solo cuando ambas capas concuerdan: el motor le ha asignado una clave, y seatStyles tiene una entrada para esa clave.

La superposición es una capa DOM

Lo que en realidad aparece en pantalla es una superposición DOM: un único contenedor <div> posicionado de forma absoluta, añadido al elemento anfitrión del renderer, con un pequeño nodo <div> por cada asiento visible y asignado. No se pinta en el escenario WebGL ni Canvas2D. Como solo lee la transformación de viewport compartida (scale / translate), funciona de forma idéntica tanto bajo el renderer WebGL como bajo el respaldo Canvas2D. El contenedor usa pointer-events: none, por lo que nunca intercepta la entrada del canvas.

Inicio rápido

El flujo de extremo a extremo más pequeño: construir un renderer con un estado personalizado en theme.seatStyles, cargar un evento, luego asignar ese estado a algunos asientos después de que los asientos existan.

import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';

const element = document.getElementById('seatmap') as HTMLElement;

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  theme: {
    seatStyles: {
      mykey: {
        tint: 'rgba(76,141,255,0.5)',
        keepSeatName: true,
      },
    },
  },
});

void renderer.loadEvent('event-guid').then(() => {
  renderer.setSeatsState([101, 102, 103], 'mykey');
});

Notas:

  • publicKey es obligatorio en tiempo de ejecución para SeatmapBookingRenderer; el constructor lanza 'Public key is undefined' si falta.
  • setSeatsState debe llamarse después de que los asientos existan – es decir, después de que loadEvent se resuelva (o desde dentro de un callback onSchemaDataLoaded). Los estados se aplican a asientos que ya se han cargado.
  • Los nodos de superposición son diminutos con el zoom de ajuste al recinto. Para hacerlos claramente visibles, acerca el zoom a una sección con zoomToSection (consulta los ejemplos trabajados).

Conceptos

Semántica del motor de asignación

  • Un estado por asiento. Cada asiento tiene exactamente una clave asignada, o ninguna.
  • Sobrescritura, no fusión. Asignar un nuevo estado a un asiento descarta cualquier asignación previa. Asignar reserved y luego sold al mismo asiento lo deja como sold.
  • La limpieza es por asiento y silenciosa ante fallos. Limpiar el estado en IDs que no estaban asignados simplemente los omite; una limpieza parcial deja los demás asientos intactos.
  • El bloqueo es una compuerta aparte. Un asiento se trata como bloqueado solo cuando tiene una asignación y el estilo de esa clave establece blockInteraction como verdadero. El estado por sí solo nunca implica bloqueo.

Claves integradas frente a claves personalizadas

Las claves de estado se dividen en dos grupos.

Las claves integradas son los propios estados de asiento del renderer, enumerados por BuiltinSeatStateKey:

type BuiltinSeatStateKey =
  | 'default'
  | 'unavailable'
  | 'filtered'
  | 'hovered'
  | 'selected'
  | 'loading'
  | 'error';

Estas se configuran con el tipo más rico ISeatStyle (tamaño, color, trazo, sombra, sobrescritura accesible). Los temas de booking y admin que vienen incluidos ya las pueblan. En particular, el size de la entrada default es la huella que usa cada nodo de superposición (consulta Notas de rendimiento y renderizado).

Las claves personalizadas son cualquier otra cadena: 'reserved', 'held', 'vip', 'sold', 'restricted', etcétera. Las asignas mediante setSeatsState y las estilizas con el más ligero ICustomSeatStyle (o un ISeatStyle completo si lo prefieres).

La configuración de estilos

Toda la estilización de asientos vive en theme.seatStyles, tipada como SeatStylesMap.

Forma de SeatStylesMap

type SeatStylesMap = Partial<Record<BuiltinSeatStateKey, ISeatStyle>> & {
  [customKey: string]: ICustomSeatStyle | ISeatStyle | undefined;
};
  • La mitad Partial<Record<BuiltinSeatStateKey, ISeatStyle>> restringe las siete claves integradas a ISeatStyle (el tipo más rico, que añade la sobrescritura accessible). Las siete son opcionales.
  • La mitad de la firma de índice abre el mapa a cualquier clave de cadena arbitraria, cuyo valor puede ser un ICustomSeatStyle (ligero: tint, svg, callback de render, etc.) o un ISeatStyle completo. El undefined en la unión es requerido por TypeScript al mezclar un Partial<Record<...>> con una firma de índice general.

En la práctica: las claves integradas obtienen ISeatStyle; las claves personalizadas suelen usar el más ligero ICustomSeatStyle.

IBasicSeatStyle

interface IBasicSeatStyle {
  size: number;
  color: string;
  seatName?: {
    font: string;
    color: string;
  };
  stroke?: {
    width: number;
    color: string;
    align: 'center' | 'inside' | 'outside';
  };
  imageId?: string;
  shadow?: {
    blur: number;
    color: string;
    x?: number;
    y?: number;
  };
}
Campo Tipo Opcional Significado
size number No Tamaño de renderizado del asiento (diámetro/radio en la unidad del renderer).
color string No Color de relleno del asiento.
seatName { font: string; color: string } Tipografía de la etiqueta del asiento. font es una cadena de fuente CSS (por ejemplo '18px Arial'); color es el color del texto.
stroke { width: number; color: string; align: 'center' | 'inside' | 'outside' } Borde del asiento: grosor, color y alineación respecto al borde del asiento.
imageId string ID de un recurso de imagen preregistrado dibujado sobre el asiento.
shadow { blur: number; color: string; x?: number; y?: number } Sombra paralela: radio de desenfoque, color y desplazamiento X/Y opcional.

ISeatStyle

interface ISeatStyle extends IBasicSeatStyle {
  accessible?: IBasicSeatStyle;
}
Campo Tipo Opcional Significado
(hereda todos los campos de IBasicSeatStyle) Consulta la tabla anterior.
accessible IBasicSeatStyle Estilo de sobrescritura aplicado cuando el asiento está marcado como accesible/para silla de ruedas. Lleva los mismos seis campos base, menos el propio accessible.

ICustomSeatStyle

interface ICustomSeatStyle {
  tint?: string;
  svg?: string;
  imageId?: string;
  className?: string;
  priority?: number;
  blockInteraction?: boolean;
  keepSeatName?: boolean;
  render?: (args: ISeatStateRenderArgs) => HTMLElement | string;
}
Campo Tipo Opcional Significado
tint string Tinte de color superpuesto sobre la huella del asiento (renderizado como un círculo relleno).
svg string SVG a dibujar sobre el asiento. Si empieza con <, se trata como marcado SVG en línea; de lo contrario se usa como URL.
imageId string ID de un recurso de imagen preregistrado (buscado en theme.images[imageId]).
className string Nombre de clase CSS añadido al <div> de superposición del asiento (para estilización / animación personalizada).
priority number Aceptado por el tipo pero actualmente no consumido por la superposición. No hay orden en z, y establecerlo no tiene efecto hoy en día.
blockInteraction boolean Cuando es true, el asiento se reporta como bloqueado (consulta Bloqueo de interacción).
keepSeatName boolean Controla la etiqueta del asiento en el nodo de superposición. La etiqueta se muestra por defecto; solo keepSeatName: false la suprime.
render (args: ISeatStateRenderArgs) => HTMLElement | string Callback DOM personalizado que suministra por completo el contenido de la superposición del asiento.

Nota: ICustomSeatStyle no tiene un size propio. Los nodos de superposición comparten la huella de asiento por defecto – el tamaño proviene de seatStyles['default']?.size (con respaldo a 10 si no hay entrada default).

ISeatStateRenderArgs

El único argumento pasado a un callback render personalizado:

interface ISeatStateRenderArgs {
  seat: ISeat;
  stateKey: string;
  style: ICustomSeatStyle;
  sizePx: number;
}
Campo Tipo Significado
seat ISeat El objeto de datos completo del asiento que se está renderizando.
stateKey string La clave de estado activa (integrada o personalizada) que disparó este renderizado.
style ICustomSeatStyle El ICustomSeatStyle resuelto para este estado, para que el callback pueda inspeccionar su propia configuración.
sizePx number El tamaño renderizado actual del asiento en píxeles, para que el callback pueda escalar su salida.

La API pública

Ambos métodos son públicos en el renderer base, por lo que SeatmapBookingRenderer y SeatmapAdminRenderer los exponen de forma idéntica. Aplica estados solo después de que el evento y sus asientos se hayan cargado.

setSeatsState

setSeatsState(seats: ISeat[] | number[] | string[], stateKey: string): void

Asigna stateKey a los asientos dados: resuelve los IDs de asiento, registra la asignación en el motor (sobrescribiendo cualquier estado previo), escribe customState en cada objeto de asiento coincidente, luego redibuja para que la superposición se resincronice. Si seats está vacío, la llamada no hace nada.

clearSeatsState

clearSeatsState(seats: ISeat[] | number[] | string[]): void

Limpia el estado de los asientos dados: resuelve los IDs, borra la asignación del motor (los fallos se omiten silenciosamente), elimina customState de cada objeto de asiento coincidente, luego redibuja. Si seats está vacío, la llamada no hace nada.

Formas de argumento aceptadas

Ambos métodos aceptan la misma unión para seats:

  • ISeat[] – objetos de asiento (resueltos por su .id).
  • number[] – IDs numéricos de asiento en bruto.
  • string[] – claves compuestas de asiento (resueltas a IDs internamente).

El despachador husmea el tipo del primer elemento para decidir cómo resolver los IDs.

Limpiar todos los asientos

No hay una sobrecarga “limpiar todo” sin argumentos en el renderer. Para limpiar todos los asientos, pasa todos los IDs de asiento explícitamente mediante getSeats:

renderer.clearSeatsState(renderer.getSeats().map((s) => s.id));

Definir estados personalizados

Un estado personalizado es una entrada de seatStyles bajo una clave arbitraria. Combina los campos de ICustomSeatStyle:

  • Tint / color. tint superpone un círculo relleno de un color CSS sobre la huella del asiento. (Para claves integradas estilizadas con ISeatStyle, color es el relleno del asiento.)
  • Size. Un estado personalizado no tiene tamaño independiente; comparte la huella de asiento default (seatStyles['default']?.size, respaldo 10). ISeatStateRenderArgs.sizePx reporta el valor resuelto.
  • Stroke. Disponible en IBasicSeatStyle / ISeatStyle (width, color, align). Usa una entrada ISeatStyle completa si la necesitas.
  • Shadow. Disponible en IBasicSeatStyle / ISeatStyle (blur, color, x, y).
  • Nombre de asiento / keepSeatName. El nodo de superposición muestra la etiqueta del asiento por defecto; establece keepSeatName: false para suprimirla. La tipografía de seatName (font, color) forma parte del ISeatStyle integrado.
  • className. Añade una clase CSS al <div> de superposición para que puedas estilizarlo o animarlo desde tu hoja de estilos.
  • imageId. Dibuja un recurso de imagen preregistrado, resuelto desde theme.images[imageId].
  • priority. Declarado en ICustomSeatStyle pero actualmente no leído por la superposición. No se implementa ningún orden en z, y como cada asiento mantiene como máximo un estado, nunca hay más de un estado personalizado en un asiento que ordenar. Establecerlo no tiene efecto hoy en día.
  • blockInteraction. Reportado a través de la compuerta de bloqueo (consulta Bloqueo de interacción).

Ejemplo de estados personalizados:

const seatStyles: SeatStylesMap = {
  restricted: {
    tint: 'rgba(214,88,78,0.6)',
    blockInteraction: true,
    keepSeatName: false,
  },
  promo: {
    tint: 'rgba(76,141,255,0.5)',
    className: 'promo-seat',
  },
};

Sobrescribir estados de asiento integrados

El mismo mapa theme.seatStyles que declara claves personalizadas también sobrescribe los propios estados integrados del renderer. La diferencia respecto a las claves personalizadas es dónde se dibujan y qué los impulsa: las claves integradas reestilizan el glifo de asiento pintado en el escenario del canvas, y el renderer las aplica automáticamente a partir de la propia condición de cada asiento (locked, filtered, hovered, selected, etcétera). No llamas a setSeatsState para ellas.

Clave integrada Se aplica cuando el asiento está…
default disponible, sin hover ni seleccionado
unavailable bloqueado / no en venta
filtered filtrado fuera de la vista actual
hovered bajo el puntero (e interactivo)
selected seleccionado / en el carrito
loading en estado de carga
error en estado de error

Tu seatStyles se fusiona en profundidad sobre el tema por defecto incluido en la construcción. Por lo tanto puedes sobrescribir un único campo de un estado integrado y heredar el resto – no hay necesidad de reformular todo el estilo. Por ejemplo, para hacer los asientos seleccionados más grandes y verdes con un borde más grueso, y recolorear los asientos no disponibles:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  theme: {
    seatStyles: {
      selected: {
        size: 46,
        color: '#1f9d55',
        stroke: { width: 3, color: '#0b5d2e', align: 'outside' },
      },
      unavailable: { size: 8, color: '#d0d0d0' },
    },
  },
});

Notas:

  • Las claves integradas toman el más rico ISeatStyle (size, color, seatName, stroke, imageId, shadow, accessible). Suministra solo los campos que quieras cambiar; el resto proviene del tema por defecto mediante la fusión en profundidad.
  • Esto estiliza el glifo de asiento del canvas, independiente de la superposición DOM de estado personalizado. Ambos se componen: un asiento puede dibujarse como selected en el canvas y aun así llevar una superposición de estado personalizado encima.
  • default.size también define la huella de cada nodo de superposición de estado personalizado (consulta Notas de rendimiento y renderizado), por lo que cambiarlo redimensiona tanto los asientos base como las superposiciones.

Receta: colores por precio, imágenes de estado y números de asiento juntos

Una necesidad común es colorear los asientos por nivel de precio (para que los compradores vean el precio) y superponer un estado transaccional – reserved, held, sold – como un ícono o máscara SVG encima, manteniendo el número de asiento legible. Dibujado como un único glifo de canvas, eso se pelea consigo mismo:

  1. Una imagen dibujada directamente sobre el asiento (mediante imageId en el estilo del asiento, o una sobrescritura onBeforeSeatDraw) reemplaza el glifo completo, por lo que el número de asiento desaparece – la ruta de dibujo del asiento retorna en cuanto se dibuja la imagen y nunca pinta la etiqueta.
  2. Como esa imagen es el glifo del asiento, se dibuja al tamaño en píxeles propio de la imagen; dimensionarla para llenar el círculo del asiento es incómodo.
  3. Establecer la imagen por asiento a través de onBeforeSeatDraw retorna un ISeatStyle nuevo, que reemplaza todo el estilo y descarta el color de precio/categoría que estableciste con setSeatsCategory.

La superposición de estado personalizado evita los tres problemas, porque es una capa DOM aparte dibujada encima del canvas, no un reemplazo del glifo del asiento:

  • El color de precio permanece. setSeatsState nunca toca seat.special.category ni el mapa de colores de categoría, por lo que un asiento conserva el color que le asignaste con setSeatsCategory.
  • La imagen llena el asiento. La superposición dibuja el svg / imageId del estado en la huella completa del asiento (inset: 0, ancho y alto iguales al tamaño de asiento por defecto).
  • El número queda encima. El número de asiento se redibuja centrado sobre la imagen por defecto (keepSeatName por defecto es true); establece keepSeatName: false solo cuando quieras que el ícono reemplace el número (el candado en sold, por ejemplo).

Así que reemplaza el enfoque por asiento onBeforeSeatDraw con dos llamadas independientes y componibles:

// 1) Price-tier color, painted on the canvas seat -- persists.
renderer.setSeatsCategory(seatIds, priceCategoryId, '#1f9d55');

// 2) Transactional state overlay on top -- seat number preserved.
renderer.setSeatsState(seatIds, 'reserved');

con el estado declarado una vez en la construcción. Omite tint para que el color de precio se vea a través de las partes transparentes de la máscara:

const seatStyles: SeatStylesMap = {
  reserved: {
    keepSeatName: true, // default -- seat number stays visible on top
    svg:
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
      '<circle cx="50" cy="50" r="42" fill="none" stroke="#fff" stroke-width="6"/></svg>',
  },
};

El asiento conserva su color de precio, el SVG llena el asiento como una máscara de superposición, y el número se renderiza encima – sin onBeforeSeatDraw. Para recolorear el asiento según el estado en lugar de dejar que el color de precio se transparente, añade un tint semitransparente. Para referenciar un recurso con nombre en lugar de SVG en línea, regístralo bajo theme.images y apunta imageId a él:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  theme: {
    images: { holdIcon: 'data:image/svg+xml;utf8,...' }, // or a URL
    seatStyles: {
      held: { imageId: 'holdIcon', keepSeatName: true },
    },
  },
});

La superposición es diminuta con el zoom de ajuste al recinto y está oculta en la vista detallada de una sola sección; consulta Notas de rendimiento y renderizado si un estado no aparece. Como cada asiento con estado es un nodo DOM aparte, mantén acotado el número de asientos con estado visibles a la vez – la superposición no se agrupa en lotes como los asientos del canvas, por lo que grandes cantidades degradan el rendimiento.

DOM personalizado mediante render()

Cuando los campos declarativos no bastan, suministra un callback render. Provee por completo el contenido del nodo de superposición para ese estado.

render?: (args: ISeatStateRenderArgs) => HTMLElement | string;

El callback recibe un ISeatStateRenderArgs (seat, stateKey, style, sizePx) y devuelve uno de los siguientes:

  • un HTMLElement – añadido directamente al nodo de superposición; usa esto para contenido complejo o interactivo, o
  • una string – tratada como marcado SVG en bruto. El renderer la envuelve en un <div> posicionado de forma absoluta (inset: 0) y la renderiza mediante un <img src="data:image/svg+xml;utf8,..."> dimensionado para llenar el nodo.

Cuando render está presente, asume la generación de contenido del nodo; la ruta declarativa tint / svg / imageId no se usa para ese estado. Usa render para visuales dinámicos, dirigidos por datos (por ejemplo, dibujar algo basado en seat o sizePx). Prefiere los campos declarativos para visuales estáticos – son más simples y no necesitan callback.

const seatStyles: SeatStylesMap = {
  badge: {
    render: ({ sizePx }: ISeatStateRenderArgs): string =>
      `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${sizePx} ${sizePx}">` +
      `<circle cx="${sizePx / 2}" cy="${sizePx / 2}" r="${sizePx / 3}" fill="#fff"/></svg>`,
  },
};

Animaciones

Hay dos rutas de animación.

Animaciones SVG autónomas (SMIL)

Los estados personalizados canónicos se animan por completo dentro de la propia cadena SVG, usando elementos SMIL (<animate>, <animateTransform>). Esto no necesita CSS externo, ni @keyframes, ni className – por ejemplo, un <animate> sobre r y stroke-opacity de un círculo produce un anillo pulsante. Este es el enfoque usado por los ejemplos trabajados a continuación.

Animaciones CSS mediante className

Si prefieres animación dirigida por CSS, establece className en el estado personalizado. El nodo de superposición recibe esa clase en su <div>, y lo animas desde tu propia hoja de estilos:

.promo-seat {
  animation: promo-pulse 1.4s ease-in-out infinite;
}

@keyframes promo-pulse {
  0%,
  100% {
    opacity: 0.4;
  }
  50% {
    opacity: 1;
  }
}
const seatStyles: SeatStylesMap = {
  promo: { tint: 'rgba(76,141,255,0.5)', className: 'promo-seat' },
};

Bloqueo de interacción

Un asiento se reporta como bloqueado solo cuando se cumplen ambas condiciones:

  1. El asiento tiene una clave de estado asignada.
  2. seatStyles[stateKey] existe y tiene blockInteraction como verdadero.

Por lo tanto:

  • Un asiento sin asignación nunca está bloqueado.
  • Un asiento cuyo estilo carece de blockInteraction (o lo tiene como falso) no está bloqueado.
  • Si el mapa seatStyles está ausente, nada está bloqueado.

Concretamente, con { sold: { blockInteraction: true }, held: { tint: '#4c8dff' } }: un asiento en sold está bloqueado, un asiento en held no lo está.

keepSeatName es independiente del bloqueo. La etiqueta se muestra por defecto; solo keepSeatName: false la suprime. En los ejemplos trabajados a continuación, sold establece keepSeatName: false (el ícono de candado reemplaza el número), mientras que reserved, held y vip establecen keepSeatName: true.

Booking vs Admin Renderer

setSeatsState y clearSeatsState están definidos en el renderer base y son heredados sin cambios tanto por SeatmapBookingRenderer como por SeatmapAdminRenderer. La API, las formas de argumento y el comportamiento son idénticos en ambos.

Los constructores difieren solo en sus tipos de ajustes.

SeatmapBookingRenderer:

constructor(
  element: HTMLElement,
  settings?: IBookingRendererSettings,
  tags?: Record<string, string | number | boolean>,
)

SeatmapAdminRenderer:

constructor(element: HTMLElement, settings?: IAdminRendererSettings)

Ambos tipos de ajustes aceptan settings.theme.seatStyles.

Por qué una demo corre sobre el admin renderer

Cuando quieres demostrar y verificar visualmente estados personalizados, el admin renderer es la superficie más fácil – por visibilidad de renderizado, no por capacidad de la API. En el booking renderer, la disponibilidad de asientos para un evento real determina la apariencia: los asientos de demo que en realidad no están disponibles se leen como no disponibles, y la estilización de no disponible puede ocultar la superposición de estado personalizado. El admin renderer no aplica esa estilización de disponibilidad de booking, por lo que los estados se muestran limpiamente. En producción, el booking renderer expone exactamente los mismos métodos para asientos genuinamente disponibles.

Ejemplos trabajados

Lo siguiente reproduce cuatro estados canónicos y el flujo completo. Es copiable y pegable.

Los cuatro estados en theme.seatStyles

const seatStyles: SeatStylesMap = {
  reserved: {
    tint: 'rgba(224,161,60,0.55)',
    keepSeatName: true,
    svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#fff" stroke-width="6"><animate attributeName="r" values="34;46;34" dur="1.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.9;0.3;0.9" dur="1.4s" repeatCount="indefinite"/></circle></svg>',
  },
  held: {
    tint: 'rgba(76,141,255,0.5)',
    keepSeatName: true,
    svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="42" fill="none" stroke="#fff" stroke-width="5"><animate attributeName="stroke-opacity" values="0.15;0.85;0.15" dur="2.2s" repeatCount="indefinite"/></circle></svg>',
  },
  vip: {
    tint: 'rgba(155,108,255,0.5)',
    keepSeatName: true,
    svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#fff" stroke-width="5" stroke-dasharray="30 200" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="2.4s" repeatCount="indefinite"/></circle></svg>',
  },
  sold: {
    tint: 'rgba(214,88,78,0.6)',
    blockInteraction: true,
    keepSeatName: false,
    svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fff" d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5m0 2a3 3 0 0 1 3 3v3H9V6a3 3 0 0 1 3-3"/></svg>',
  },
};

Resumen por estado:

  • reservedtint, keepSeatName: true, anillo pulsante animado (SMIL <animate> sobre r y stroke-opacity, 1.4 s).
  • heldtint, keepSeatName: true, anillo que se desvanece (SMIL <animate> sobre stroke-opacity, 2.2 s).
  • viptint, keepSeatName: true, anillo punteado giratorio (SMIL <animateTransform> rotate, 2.4 s).
  • soldtint, blockInteraction: true, keepSeatName: false, ícono de candado estático (sin animación).

Ninguno de los cuatro estados usa color, size, stroke, render ni className.

Elegir una sección de demo

Agrupa los asientos por su sección y devuelve la primera sección con al menos 44 asientos, de lo contrario la más grande:

const pickCustomStateDemoSection = (
  renderer: SeatmapAdminRenderer,
): { sectionId: number; ids: number[] } => {
  const bySection = new Map<number, number[]>();
  for (const seat of renderer.getSeats()) {
    const list = bySection.get(seat.sectorId) ?? [];
    list.push(seat.id);
    bySection.set(seat.sectorId, list);
  }
  let best: { sectionId: number; ids: number[] } | null = null;
  for (const [sectionId, ids] of bySection) {
    if (ids.length >= 44) return { sectionId, ids };
    if (!best || ids.length > best.ids.length) best = { sectionId, ids };
  }
  return best ?? { sectionId: 0, ids: [] };
};

El flujo de demo mixto

Limpia todos los asientos, aplica los cuatro estados en porciones disjuntas de la sección de demo, luego acerca el zoom:

const demoMix = (renderer: SeatmapAdminRenderer): void => {
  const { sectionId, ids } = pickCustomStateDemoSection(renderer);
  const pick = (from: number, to: number): number[] => ids.slice(from, to);
  renderer.clearSeatsState(renderer.getSeats().map((s) => s.id));
  renderer.setSeatsState(pick(0, 12), 'reserved');
  renderer.setSeatsState(pick(12, 24), 'held');
  renderer.setSeatsState(pick(24, 32), 'vip');
  renderer.setSeatsState(pick(32, 44), 'sold');
  renderer.zoomToSection(sectionId, { focus: true });
};

La llamada zoomToSection(sectionId, { focus: true }) es lo que hace visibles los pequeños nodos de superposición.

Acciones de un solo estado

Cada acción de un solo estado aplica un estado a su porción (sin limpiar primero), luego acerca el zoom:

const applyReserved = (renderer: SeatmapAdminRenderer): void => {
  const { sectionId, ids } = pickCustomStateDemoSection(renderer);
  renderer.setSeatsState(ids.slice(0, 12), 'reserved');
  renderer.zoomToSection(sectionId, { focus: true });
};

Las acciones held, vip y sold siguen la misma forma, cada una sobre su propia porción de ids: held sobre ids.slice(12, 24), vip sobre ids.slice(24, 32), y sold sobre ids.slice(32, 44).

Limpiar todos los estados

const clearStates = (renderer: SeatmapAdminRenderer): void => {
  const ids = renderer.getSeats().map((s) => s.id);
  renderer.clearSeatsState(ids);
};

Notas de rendimiento y renderizado

La superposición mantiene el coste del DOM proporcional a lo que de verdad está en pantalla – pero es DOM real, así que el número de asientos con estado visibles al mismo tiempo gobierna el rendimiento.

  • El coste escala con el número de asientos con estado visibles – mantenlo acotado. Cada asiento asignado visible monta su propio nodo <div>, más un <img> interno para un svg / imageId, una capa tint opcional, y el nodo del número de asiento. A diferencia de la capa de asientos WebGL, estos son elementos DOM individuales, no agrupados en lotes – y los SVG animados (SMIL) ejecutan cada uno su propia animación. Un puñado o unas pocas docenas de asientos con estado en pantalla está bien; aplicar estados a cientos o miles de asientos visibles a la vez – toda una sección grande, o cualquier conjunto de estados con un zoom donde gran parte del recinto esté a la vista – crea esa cantidad de nodos y degrada significativamente la tasa de fotogramas, tanto más con animación. Para estilización que deba escalar a todo el recinto, prefiere las rutas agrupadas en el canvas: colores de precio/categoría mediante setSeatsCategory, y sobrescrituras de estados integrados en seatStyles. Reserva la superposición de estado personalizado para un conjunto acotado de asientos que el usuario esté de verdad mirando, y prefiere visuales estáticos (no animados) cuando muchos asientos lleven un estado a la vez.
  • Virtualización. Un nodo se monta solo para asientos en la intersección del conjunto visible y el conjunto asignado. Los asientos fuera del viewport nunca se montan; a los asientos que salen de la vista por desplazamiento se les eliminan los nodos.
  • Contenedor perezoso. No se crea ningún <div> contenedor hasta que se asigna al menos un estado. Cuando no quedan estados, la superposición se desmonta por completo – nodos eliminados, contenedor retirado del DOM. Limpiar todos los estados elimina la superposición por completo.
  • Supresión en vista de sección. Cuando el renderer entra en la vista detallada de una sola sección, la superposición se suprime (display: none) y se omite la reconciliación. Reaparece cuando se levanta la supresión. Esto está vinculado a la vista de detalle de sección, no al modo 3D.
  • Diminuta con el ajuste al recinto. El tamaño del nodo proviene de seatStyles['default']?.size (respaldo 10) y los nodos se posicionan en coordenadas del mundo, escaladas por la transformación de viewport compartida. Con el zoom de ajuste al recinto los nodos son muy pequeños. Por eso los ejemplos llaman a zoomToSection(sectionId, { focus: true }).
  • Independencia del escenario. La superposición lee solo estado agnóstico al escenario – la transformación de viewport, el elemento anfitrión, devicePixelRatio y theme.images para resolver imageId. Se comporta de forma idéntica bajo WebGL y el respaldo Canvas2D.

Resolución de problemas

La superposición no es visible.

  • Nivel de zoom. Con el ajuste al recinto, los nodos son diminutos. Llama a zoomToSection(sectionId, { focus: true }) (o acerca el zoom a la sección de otra manera) para hacerlos visibles.
  • Supresión en vista de sección. En la vista detallada de una sola sección el contenedor de superposición está oculto. Regresa cuando se levanta la supresión. La rotación 3D no suprime la superposición.
  • Asientos aún no cargados. El estado se aplica solo a asientos cargados. Llama a setSeatsState después de que loadEvent se resuelva (o desde onSchemaDataLoaded).
  • Clave incorrecta / no en seatStyles. Un asiento solo renderiza una superposición cuando su clave asignada tiene una entrada seatStyles coincidente. Verifica que la cadena de clave pasada a setSeatsState coincida exactamente con una clave en theme.seatStyles.

Los estados no se limpian.

  • clearSeatsState limpia solo los IDs que pasas; los fallos se omiten silenciosamente. Para limpiar todo, pasa todos los IDs: renderer.clearSeatsState(renderer.getSeats().map((s) => s.id)). Cuando se limpia el último estado, todo el contenedor de superposición se retira del DOM.

Los asientos de demo del booking renderer aparecen como no disponibles.

  • En el booking renderer, la disponibilidad real del evento estiliza los asientos no disponibles, lo que puede ocultar la superposición de estado personalizado. Demuestra los estados personalizados en SeatmapAdminRenderer, que no aplica la estilización de disponibilidad de booking. En producción, en el booking renderer, aplica estados personalizados a asientos genuinamente disponibles.

Referencia rápida de la API

Símbolo Firma / forma Dónde
setSeatsState (seats: ISeat[] | number[] | string[], stateKey: string): void Renderer (booking + admin)
clearSeatsState (seats: ISeat[] | number[] | string[]): void Renderer (booking + admin)
setSeatsCategory (seats: ISeat[] | number[] | string[], category: number, color?: string): void Renderer (booking + admin)
getSeats () => ISeatDTO[] (se usa para enumerar asientos al limpiar todo) Renderer
zoomToSection (sectionId, { focus: true }) Renderer
loadEvent booking: (eventId: string, sectorId?: number) => Promise<void>; admin: (eventId: string) => Promise<void> renderers
BuiltinSeatStateKey 'default' | 'unavailable' | 'filtered' | 'hovered' | 'selected' | 'loading' | 'error' type
SeatStylesMap Partial<Record<BuiltinSeatStateKey, ISeatStyle>> & { [customKey: string]: ICustomSeatStyle | ISeatStyle | undefined } type
IBasicSeatStyle { size; color; seatName?; stroke?; imageId?; shadow? } interface
ISeatStyle IBasicSeatStyle & { accessible?: IBasicSeatStyle } interface
ICustomSeatStyle { tint?; svg?; imageId?; className?; priority?; blockInteraction?; keepSeatName?; render? } interface
ISeatStateRenderArgs { seat: ISeat; stateKey: string; style: ICustomSeatStyle; sizePx: number } interface
IRendererTheme.seatStyles seatStyles?: SeatStylesMap theme field
IRendererTheme.images images?: { [id: string]: string } (recursos SVG/imagen con nombre, resueltos por imageId) theme field

Relacionado

  • Estados de asiento y tipos de hold – el ciclo de vida del tipo de hold en el backend y el modelo de datos (state / holdType en el booking service). Ese artículo cubre cómo los asientos avanzan por su ciclo de vida del lado del servidor; este cubre cómo visualizar y aplicar estados del lado del cliente mediante el Renderer SDK.
  • Estados y estilización de secciones – el concepto equivalente aplicado a los contornos de sección (highlighted, selected, unavailable, filtered).
  • Inicializar el SDK – instala @seatmap.pro/renderer, construye un renderer y carga un evento antes de aplicar cualquier estado personalizado.