Skip to main content

05. Arquitectura de Frontend: MDX-Cloud

Esta sección especifica la estructura, estándares de codificación, integración con hardware periférico y el sistema de notificaciones de la interfaz gráfica de MDX-Cloud, construida como una Single Page Application (SPA) desacoplada en React, Vite y TypeScript.

05.1 Stack Tecnológico Core

La selección de librerías prioriza el rendimiento crítico, la resiliencia Offline-First y el diseño adaptativo profesional:

  • Entorno de Construcción: Vite + TypeScript (Tipado estricto, compilación en milisegundos).
  • Estilos y UI: Tailwind CSS + shadcn/ui (Diseño plano, corporativo y componentes sin dependencias pesadas).
  • Manejador de Base de Datos Local: Dexie.js (Wrapper optimizado de IndexedDB para catálogos y colas de pedidos offline).
  • Sincronización Asíncrona: TanStack Query (React Query) para control de caché celular, reconexión automática e invalidación de estados.
  • Tiempo Real: @microsoft/signalr (Conexión persistente WebSocket para recibir Deltas de inventario y estado).
  • Cliente HTTP: axios (Capa de red estandarizada con Interceptores de seguridad para inyección JWT y Silent Refresh).
  • Generación de Entidades Offline: uuid (Módulo UUIDv7 para crear IDs secuenciales nativos en el frontend).
  • Formularios y Validación: React Hook Form + Zod (Validación en la fuente con tipado idéntico al backend de .NET 10).
  • Iconografía: Lucide React (Vectores limpios de trazo tecnológico).

05.2 Estructura de Directorios Feature-Driven

Para mantener la cohesión modular con el backend, el proyecto organiza su código fuente por características de negocio en lugar de tipos de archivos:

src/
├── assets/ # Identidad corporativa de BitOne, MDX-Cloud e ilustraciones.
├── db/ # mdxStore.ts (Instanciación central de Dexie.js).
├── components/ # UI Atómica global (Botones, inputs, tablas genéricas de shadcn).
├── config/ # Interceptores de Axios (Inyección de JWT Bearer y headers UUID).
├── layouts/ # Layout horizontal superior (Estructura de escritorio sin sidebar).
├── hooks/ # Custom hooks utilitarios globales (ej. useOnlineStatus).
├── context/ # Proveedores de estado global (AuthContext, NotificationContext).
├── services/ # Service Worker y clientes de SignalR / WebSockets.
├── features/ # CAPA NÚCLEO DE NEGOCIO (Espejo de los 10 Esquemas de BD)
│ ├── catalogos/ # Gestión de maestros (productos, presentaciones).
│ ├── fiscal/ # Catálogos del SAT y regímenes.
│ ├── ventas/ # Captura de pedidos B2B y catálogo digital.
│ ├── compras/ # Órdenes de compra a proveedores.
│ ├── wms/ # Picking móvil, lotes, FEFO y ajustes físicos.
│ ├── credito_cobranza/ # Estados de cuenta, cartera vencida y abonos.
│ ├── tesoreria/ # Programación de pagos (Fase 2).
│ ├── cfdi/ # POS de mostrador, división de folios y consumo PAC.
│ ├── personal/ # Expedientes de empleados y control RBAC.
│ └── auditoria/ # Visores forenses de logs del sistema.
├── App.tsx # Enrutador central de la aplicación (React Router).
└── main.tsx # Inicializador y montado en el DOM.

05.3 Estándar de Almacenamiento Local e IndexedDB (Dexie.js)

Para soportar la resiliencia operativa en zonas del almacén con baja cobertura de red, el frontend utiliza Dexie.js como capa de abstracción sobre IndexedDB. Con el fin de evitar colisiones de esquemas, facilitar la depuración en el navegador y simplificar el motor de sincronización, se establece la siguiente política de nomenclatura obligatoria:

A. Convención de Nombres para la Base de Datos (Database Name)

El identificador de la base de datos local en el navegador debe seguir la estructura: mdx_[modulo]_[ambiente]

  • Ejemplo para Almacén (Producción): mdx_wms_prod
  • Ejemplo para Almacén (Desarrollo): mdx_wms_dev

💡 Nota: Aislar la base de datos por módulo garantiza que el almacenamiento local de las PDAs de almacén no interfiera ni comparta memoria con los navegadores del módulo comercial o de facturación.

B. Prefijos Obligatorios para Almacenes de Objetos (Stores / Tables)

Las tablas internas de IndexedDB no se deben nombrar de forma idéntica a las de PostgreSQL. Deben llevar un prefijo semántico que indique su comportamiento en el flujo de sincronización:

PrefijoPropósitoComportamiento del Motor de SincronizaciónEjemplo
cache_Datos de solo lectura descargados del servidor.Se sobrescriben o actualizan mediante deltas desde el backend. React jamás los edita localmente.cache_productos, cache_lotes
pending_Mutaciones o transacciones creadas en modo offline.Cola de salida activa. El middleware offline busca registros aquí para empujarlos al backend en cuanto vuelve la red.pending_movimientos, pending_pickings
meta_Configuración interna, estados de sincronización y banderas.Datos puramente locales de la app para control de versiones y última fecha de sincronización (Last-Modified).meta_sync_state, meta_user_session

C. Implementación Estructurada en TypeScript (Dexie Boilerplate)

Toda instancia de Dexie dentro del frontend debe seguir el patrón de tipado estricto mapeando los prefijos:

import Dexie, { type Table } from 'dexie';

// 1. Definición de Interfaces según el prefijo
export interface ICacheProducto {
id: string; // UUIDv4 del backend
codigoBarras: string;
nombreComercial: string;
requiereRedFria: boolean;
}

export interface IPendingMovimiento {
idLocal: string; // UUIDv4 generado por React antes de enviar
loteId: string;
cantidad: number;
tipo: 'INGRESO' | 'EGRESO';
createdAt: number; // Timestamp para manejo de conflictos
}

// 2. Clase de Base de Datos con la convención de nombres
class MdxWmsDatabase extends Dexie {
cache_productos!: Table<ICacheProducto, string>;
pending_movimientos!: Table<IPendingMovimiento, string>;

constructor() {
// Nombre de la base de datos según el estándar mdx_[modulo]_[ambiente]
const dbName = `mdx_wms_${import.meta.env.MODE}`;
super(dbName);

// Definición de índices (solo los campos por los que se harán búsquedas/filtros)
this.version(1).stores({
cache_productos: 'id, codigoBarras',
pending_movimientos: 'idLocal, loteId, createdAt'
});
}
}

export const db = new MdxWmsDatabase();

D. Seguridad y Volatilidad (Reglas de Oro)

Para garantizar la privacidad de los datos y la estabilidad de la SPA en entornos hostiles, el equipo de desarrollo debe adherirse a las siguientes restricciones sobre IndexedDB:

  • 1. Regla de Cero Sensibilidad: IndexedDB se guarda "en texto plano" en el disco del cliente, siendo legible desde las DevTools del navegador. Queda estrictamente prohibido almacenar Tokens de Autenticación, contraseñas o márgenes financieros corporativos.
  • 2. Estrategia de Encriptación Parcial: Si el negocio exige almacenar datos confidenciales para operación offline (ej. Límites de Crédito exactos), se deberá encriptar exclusivamente esa tabla utilizando middlewares como dexie-encrypted apoyados en la Web Crypto API. La llave de encriptación residirá solo en la memoria RAM volátil de React. Sin embargo, el catálogo público de productos jamás debe encriptarse, ya que desencriptar 10,000 productos colapsaría el procesador de las PDAs al intentar buscar.
  • 3. Manejo de Volatilidad (Auto-Healing): IndexedDB puede ser purgado por el usuario (borrar historial) o por el celular para liberar espacio. La app debe programarse con resiliencia: si la app detecta que mdx_wms_prod desapareció, debe solicitar silenciosamente el catálogo maestro a la API sin crashear.

05.4 Estrategia de Client-Side Joins y Sincronización Real-Time

Debido a que la arquitectura de base de datos minimiza las uniones complejas (JOINs) inter-esquemas en el servidor para maximizar el rendimiento, el frontend de React asume una responsabilidad arquitectónica crítica conocida como Client-Side Joins.

A. Cruces de Información Locales (Dexie.js)

El servidor de .NET 10 jamás enviará respuestas HTTP infladas.

  • Ejemplo de Retorno: Una respuesta de venta enviará ClienteId: "018f...-...", pero no enviará el nombre del cliente.
  • Obligación de React: Los componentes de UI de React deberán utilizar hooks locales para interrogar a Dexie.js y cruzar el UUID: const cliente = useLiveQuery(() => db.cache_clientes.get(pedido.ClienteId)).
  • Beneficio: Descarga monumental de la CPU y RAM del Servidor VPS.

B. Invalidación de Caché vía WebSockets (SignalR)

Para evitar que las terminales muestren información obsoleta sin tener que recargar la página, se acopla SignalR con TanStack Query:

  1. Escucha Constante: El servicio en segundo plano de React (@microsoft/signalr) mantiene la conexión abierta.
  2. Recepción del Delta: Si alguien en otra sucursal vende la última caja de Paracetamol, el servidor emite el evento StockUpdated.
  3. Invalidación Focalizada: React intercepta el evento e instruye a TanStack Query a destruir únicamente la caché de ese producto: queryClient.invalidateQueries({ queryKey: ['inventario', productoId] }).
  4. Repintado Mágico: El componente visual muta de forma inmediata sin que el usuario intervenga, previniendo colisiones lógicas.

05.5 Capa de Red e Interceptores HTTP (Axios)

Para garantizar la seguridad bancaria estipulada en el Documento 10 (cero almacenamiento de tokens en disco) y soportar la Sincronización por Deltas, toda la comunicación HTTP hacia el backend se centraliza a través de Axios. Queda prohibido el uso de fetch disperso en los componentes.

A. Interceptor de Petición (Inyección JWT)

Antes de que cualquier solicitud de Axios salga hacia la red, un interceptor global atrapa la petición, lee el Access Token (JWT) directamente de la memoria volátil de React (State) y lo inyecta dinámicamente en el encabezado Authorization: Bearer <token>.

B. Interceptor de Respuesta (Renovación Silenciosa)

El ciclo de vida del Access Token es intencionalmente corto (15 minutos). Cuando este expira, el backend de .NET rechaza la petición HTTP con un error 401 Unauthorized. El interceptor de respuesta de Axios captura este error y ejecuta una Renovación Silenciosa:

  1. Pausa temporalmente todas las peticiones salientes para evitar errores en cascada.
  2. Llama al endpoint /api/auth/refresh, el cual envía automáticamente la Cookie segura HttpOnly (Refresh Token) hacia .NET.
  3. El servidor devuelve un nuevo Access Token de 15 minutos.
  4. El interceptor lo guarda en memoria y reintenta automáticamente la petición original que había fallado.

💡 Resultado de UX: El usuario final puede estar trabajando 8 horas seguidas sin interrupciones y jamás será expulsado a la pantalla de login, manteniendo un nivel de seguridad extremo en el proceso.

C. Sincronización por Deltas (Patching de Caché)

Para evitar el colapso del plan de datos móviles en las tablets de campo y mantener un rendimiento de 0 ms:

  • Actualización Online: Cuando el precio de un producto cambia, el Backend actualiza solo ese registro y emite un evento vía SignalR. React recibe un "Delta" microscópico (ej. {"productoId":"A1", "precio": 12.50}) y actualiza únicamente esa celda en Dexie.js.
  • Recuperación Offline: Si el dispositivo de un agente pierde la señal durante 2 horas, al recuperar el internet el cliente Axios llama al endpoint de recuperación enviando la estampa de tiempo (/api/sync?since=10:00AM). El Backend responde exclusivamente con los registros exactos que mutaron durante esas 2 horas de desconexión, ahorrando miles de megabytes de transferencia inútil de catálogos sin cambios.

05.6 Topología de Frontends (Desacoplamiento B2B y Clientes)

(Última modificación: 2 de Junio de 2026)

Aunque la base de datos y el servidor API de MDX-Cloud operan bajo un modelo centralizado (Modular Monolith), la estrategia de interfaces gráficas estipula una separación estricta de proyectos Frontend (SPAs) dependiendo del actor que interactúe con el sistema.

Queda estrictamente prohibido agrupar pantallas corporativas internas y portales públicos bajo el mismo código de React. La topología oficial se divide en dos frentes físicos:

1. Frontend Interno (ERP Core / Empleados)

  • Usuarios: Almacenistas, Cajeros, Contadores, Recursos Humanos.
  • Dominio de Autenticación: personal.empleados (Roles RBAC corporativos).
  • Stack: React + Vite SPA (Pesado, cargado de lógicas de IndexedDB, funcionamiento Offline-First, múltiples librerías complejas).
  • Exposición: Privado. Diseñado para alta densidad de datos.

2. Frontend Externo (Portal B2B / Clientes)

  • Usuarios: Hospitales, Farmacias clientes, Doctores independientes.
  • Dominio de Autenticación: ventas.clientes.
  • Stack: Proyecto separado 100% independiente (Recomendado: Next.js para SEO comercial o una SPA Vite ultra-ligera).
  • Exposición: Público. Sujeto a branding comercial, optimización de velocidad de carga extrema y diseño de carrito/e-commerce intuitivo.
  • Regla de Seguridad de Código: Al ser un proyecto separado, cuando el cliente acceda al portal B2B, su navegador jamás descargará el código fuente de los módulos de Tesorería, Facturación o WMS del ERP interno. El B2B únicamente consumirá endpoints de .NET expuestos específicamente para clientes (ej. /api/b2b/mis-pedidos), bloqueando vector de ataque y garantizando el secreto industrial.

Esta separación física de interfaces (sobre un mismo cerebro Backend unificado) asegura que la experiencia del cliente final sea veloz y segura, mientras el ecosistema operativo interno mantiene su potencia y densidad sin compromisos.