Ordenamiento de matrices: ingeniería de redes B2B de alto rendimiento
Una inmersión profunda en la creación de matrices de pedidos B2B de alto rendimiento para miles de variantes. Dominio de la virtualización de React, la gestión de estados y el procesamiento por lotes de API.
En el acelerado mundo del comercio electrónico B2C, el recorrido del usuario es lineal: explorar, seleccionar, agregar al carrito. Pero B2B es una bestia completamente diferente. Un comprador mayorista de una tienda minorista de moda no quiere hacer clic en “Agregar al carrito” cincuenta veces para cincuenta tallas de camisa diferentes. Quieren eficiencia. Quieren velocidad. Quieren una Matrix.
En Maison Code Paris, hemos visto cómo los portales B2B se desmoronaban por su propio peso. Hemos visto soluciones “empresariales” que tardan 4 segundos en representar la página de un producto porque ingenuamente representan 2000 entradas para un solo tipo de tornillo. Esto es inaceptable. En la economía mayorista, la fricción no sólo molesta al usuario; destruye el ciclo de ingresos recurrentes.
Esta guía es una inmersión profunda de ingeniería en la construcción del “Excel del comercio electrónico”: The Matrix Ordering Grid.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
Por qué Maison Code analiza el ordenamiento matricial
Construimos amplias plataformas B2B para casas de lujo y gigantes industriales. La diferencia entre una herramienta que parece un SaaS moderno y una que parece un ERP heredado es a menudo Matrix Grid. Cuando un comprador puede ingresar cantidades para 500 variantes en segundos, usando la navegación por teclado, sin demoras, se siente profesional. Se sienten respetados.
Analizamos esto porque se encuentra en la intersección de fuertes restricciones técnicas (límites de DOM, límites de API) y experiencia de usuario de alto valor. Resolverlo requiere algo más que una biblioteca de interfaz de usuario; requiere una comprensión fundamental de cómo representa el navegador y cómo fluye el estado a través de una aplicación.
El problema de la escalabilidad: renderizado O(n)
Considere un producto B2B simple: una camiseta.
- Colores: 10
- Tallas: 8
- Total de variantes: 80 entradas.
React te permite renderizar 80 entradas sin sudar.
Consideremos ahora un perno industrial.
- Longitudes: 50
- Pasos de rosca: 20
- Materiales: 10
- Variantes totales: 10.000 entradas.
Si intenta representar 10,000 elementos <input> en el DOM simultáneamente, su aplicación se congelará. El cuello de botella no es la ejecución de JavaScript; son las fases Diseño y Pintura del motor del navegador. Cada vez que el usuario escribe un carácter en un cuadro, si su gestión de estado es ingenua, React podría intentar conciliar todo el árbol.
El costo de volver a renderizar
Si usa un único objeto useState para todo el formulario:
const [formState, setFormState] = useState({});
Cada pulsación de tecla activa una nueva representación del componente principal, que luego vuelve a representar 10.000 elementos secundarios. Incluso con React.memo, la diferencia de accesorios por sí sola para 10,000 componentes causará un retraso de entrada notable. En una herramienta profesional, el retraso de entrada es fatal.
Fase 1: Virtualización (La solución DOM)
El primer paso hacia el rendimiento es reconocer que el usuario no puede ver 10.000 entradas a la vez. Un monitor típico muestra quizás 50 filas de datos.
Virtualización (o “ventana”) es la técnica de renderizar solo los nodos DOM que están actualmente visibles en la ventana gráfica. A medida que el usuario se desplaza, destruimos los nodos que salen de la parte superior de la pantalla y creamos otros nuevos que ingresan por la parte inferior. El navegador cree que está desplazando un elemento de 5000 px, pero el DOM solo contiene 50 div.
Recomendamos TanStack Virtual (sin cabeza) o react-window para esta implementación.
Patrón de implementación
Así es como estructuramos una grilla virtualizada para una matriz B2B. Tratamos la cuadrícula como un sistema de coordenadas (Fila, Columna).
importar {useVirtualizer} desde '@tanstack/react-virtual';
importar {useRef} desde 'reaccionar';
// La "fuente única de verdad" para los datos matriciales
// Idealmente, normalmente aplanado o estructurado como Mapa<VariantID, Cantidad>
escriba MatrixData = Registro<cadena, número>;
exportar const VirtualizedMatrix = ({filas, columnas, datos}: MatrixProps) => {
const parentRef = useRef(nulo);
const filaVirtualizador = usarVirtualizador({
recuento: filas.longitud,
getScrollElement: () => parentRef.current,
Tamaño estimado: () => 50, // 50px de altura de fila
overscan: 5, // Representa 5 filas adicionales para un desplazamiento suave
});
regresar (
<div
ref={padreRef}
estilo = {{
altura: `600px`,
desbordamiento: 'automático',
}}
>
<div
estilo = {{
altura: `${rowVirtualizer.getTotalSize()}px`,
ancho: '100%',
posición: 'relativa',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const filaData = filas[virtualRow.index];
regresar (
<div
clave = {virtualRow.key}
estilo = {{
posición: 'absoluta',
arriba: 0,
izquierda: 0,
ancho: '100%',
altura: `${virtualRow.size}px`,
transformar: `traducirY(${virtualRow.start}px)`,
pantalla: 'flexible',
}}
>
{/* Representar columnas (celdas) aquí */}
<RowLabel etiqueta={rowData.name} />
{columnas.map((col) => (
<Entrada de matriz
clave={col.id}
VarianteId={getVariantId(rowData, col)}
/>
))}
</div>
);
})}
</div>
</div>
);
};
Conclusión clave: la virtualización reduce el tiempo de carga inicial de 3,5 s a 0,2 s para conjuntos de datos grandes. No es negociable para catálogos superiores a 500 variantes.
Fase 2: Gestión del Estado (La Solución de la Memoria)
La virtualización resuelve el problema de renderizado, pero todavía tenemos un problema de estado. Si mantenemos el estado de 10,000 entradas en React State, actualizar una requiere una optimización cuidadosa para evitar desencadenar una actualización de todo el árbol.
El enfoque de la señal
En Maison Code, preferimos Señales (a través de @preact/signals-react o suscriptores puramente granulares) o Componentes no controlados para cuadrículas matriciales.
Si usamos componentes no controlados, omitimos por completo el ciclo de renderizado de React para la acción de escribir.
- Leer:
defaultValue={data.get(id)} - Escribir:
onChange={(e) => data.set(id, e.target.value)}(mutación directa o actualización de referencia) - Enviar: Leer desde Ref/Mapa.
Sin embargo, a menudo necesitamos Estado calculado (por ejemplo, “Cantidad total: 150”). Esto nos devuelve al terreno de la reactividad.
El mejor enfoque moderno es Zustand con actualizaciones transitorias.
// tienda.ts
importar {crear} desde 'zustand';
interfaz MatrixStore {
cantidades: Registro<cadena, número>;
setQuantity: (id: cadena, cantidad: número) => void;
// Valores calculados derivados en componentes o selectores dentro de componentes
}
exportar const useMatrixStore = crear<MatrixStore>((establecer) => ({
cantidades: {},
setCantidad: (id, cantidad) => set((estado) => ({
cantidades: { ...estado.cantidades, [id]: cantidad }
})),
}));
// MatrixInput.tsx
// Este componente se suscribe SÓLO a su segmento de estado específico
const MatrixInput = ({varianteId}) => {
cantidad constante = useMatrixStore((estado) => estado.cantidades[variantId] || 0);
const setQuantity = useMatrixStore((estado) => estado.setQuantity);
regresar (
<entrada
valor={cantidad}
onChange={(e) => setQuantity(variantId, parseInt(e.target.value))}
/>
);
}
Esto garantiza que al escribir una celda solo se vuelva a representar esa celda específica (y quizás el contador “Total”), en lugar de toda la cuadrícula.
Fase 3: Microinteracciones y UX
La velocidad es técnica, pero también perceptiva. Un comprador B2B espera que la herramienta se comporte como una hoja de cálculo.
Navegación por teclado
Una interfaz con mucho mouse es demasiado lenta para la entrada masiva. Debemos implementar Navegación con teclas de flecha.
- Intro: Mover hacia abajo (estándar de hoja de cálculo).
- Pestaña: Mover a la derecha.
- Teclas de flecha: Muévete en las direcciones respectivas.
Esto requiere gestionar el enfoque mediante programación. Dado que estamos virtualizando, la entrada en la que desea centrarse puede que aún no exista en el DOM. Esta es la parte complicada. Debe desplazar el virtualizador hasta el índice antes de intentar enfocar.
Comentarios instantáneos sobre el inventario
Los usuarios no deben esperar a “Agregar al carrito” para saber que una variante está agotada. Precargamos datos de inventario en un formato ligero:
{
"variante_1": 50,
"variante_2": 0,
"variante_3": 1200
}
Mapeamos esto a un estado visual.
- En gris: Agotado (0).
- Advertencia amarilla: Stock bajo (el usuario escribió 50, el stock es 40).
- Borde rojo: Entrada no válida.
Esta validación debe realizarse de forma sincrónica en el lado del cliente.
Fase 4: carga útil de API y procesamiento por lotes
El usuario hace clic en “Agregar al pedido”. Han seleccionado 150 variantes únicas. La mayoría de las API REST (incluida la API de carrito estándar de Shopify) no están diseñadas para procesar 150 líneas de artículos en una sola solicitud HTTP POST de manera eficiente. Es posible que se agote el tiempo de espera o se excedan los límites de carga útil.
Estrategia: la cola de promesas por lotes
Nunca bloqueamos la interfaz de usuario. Mostramos una barra de progreso (“Agregando elementos… 40%”).
constante BATCH_SIZE = 50;
función asíncrona addToCartRecursive(elementos: Artículo[]) {
si (items.length === 0) regresa;
fragmento constante = items.slice(0, BATCH_SIZE);
constante restante = items.slice(BATCH_SIZE);
// Actualización optimista de la interfaz de usuario aquí
prueba {
espere api.cart.add(fragmento);
updateProgress((total - longitud restante) / total);
devolver addToCartRecursive(restante); // Siguiente lote
} captura (error) {
handlePartialFailure(fragmento, error);
}
}
Estrategia: La transformación del carrito (Shopify)
Para los comerciantes de Shopify Plus, utilizamos Funciones de transformación del carrito o Paquetes. Podemos agregar un artículo principal único (“The Matrix Bundle”) al carrito y dejar que la lógica del backend lo expanda en artículos en línea al finalizar la compra. Esto mantiene las interacciones con el carrito increíblemente rápidas y al mismo tiempo preserva la lógica de backend para el cumplimiento. Consulte nuestra guía sobre Extensibilidad de pago para obtener más información al respecto.
Móvil: el pivote
Una cuadrícula de 50x20 es imposible en el móvil. No intentes hacer que responda reduciendo las células. Es inutilizable.
En dispositivos móviles, Pivotamos la interfaz de usuario.
En lugar de Filas = Tamaños y Cols = Colores, simplemente mostramos el Atributo principal (por ejemplo, Color) como una lista.
- El usuario toca “Rojo”.
- Un acordeón se expande (o se abre una sábana bajera).
- El usuario ve una lista de tamaños para “Rojo”.
- El usuario ingresa cantidades.
- El usuario contrae “Rojo” y toca “Azul”.
Este enfoque de “profundización” respeta el espacio de la pantalla pequeña y al mismo tiempo mantiene intacta la jerarquía de datos.
Puntos de referencia de rendimiento
Cuando migramos un cliente importante de un formulario React estándar a una Matriz Zustand virtualizada, observamos:
| Métrica | Cuadrícula heredada (Reacción estándar) | Matriz de códigos de Maison (virtualizada) | Mejora | | :