2 min read
Cotizador de Modasa: arquitectura, retos y cómo escalamos la solución

Resumen

Este artículo técnico explica cómo diseñamos, desarrollamos y escalamos el “Cotizador de Modasa”, un conjunto de microproyectos (frontend, backend, transformador de datos y proxies SAP) que se integraron para ofrecer una herramienta de cotización confiable y rápida. Me centro en decisiones de arquitectura, retos operativos, optimizaciones de rendimiento y cómo se integraron los distintos repositorios.

Contrato breve (inputs / outputs / criterios de éxito)

  • Inputs: catálogos de productos, configuraciones comerciales, parámetros por cliente, tarifas e impuestos; solicitudes HTTP REST desde la UI; archivos de importación en CSV/Excel.
  • Outputs: respuesta JSON con líneas de cotización (precio neto, impuestos, opciones), PDF/Excel exportable, y registros de auditoría para trazabilidad.
  • Criterios de éxito: latencia < 300 ms para consultas simples, soporte de concurrencia para picos de 200 RPS, disponibilidad 99.9% en el plano de cotizaciones.

Topología general

La solución quedó compuesta por los siguientes servicios:

  • cotizador-frontend: SPA React 18.3 + Vite con TailwindCSS y PrimeReact que orquesta la experiencia del vendedor. Usa TanStack Query (React Query) para gestión de estado del servidor y React Router DOM para navegación.
  • cotizador-backend: API REST con Express.js corriendo sobre Bun runtime (alternativa a Node.js) que expone endpoints de cotización y persistencia. Usa MySQL2 para conexión a base de datos y Socket.io para comunicación en tiempo real.
  • cotizador-data-transformer: pipeline Python (FastAPI + Pandas) para limpiar y normalizar catálogos de Excel pivoteados (alternador, motor, integradora) y convertirlos a formato entendible por la base de datos MySQL. Incluye RQ (Redis Queue) para procesamiento asíncrono con seguimiento en tiempo real via WebSockets.
  • cotizador-sap-proxy: adaptador PHP que actúa como puente SOAP con el sistema SAP para consultas de clientes, creación de pedidos, obtención de stock y sincronización de vendedores.
  • cotizador-app: aplicación Android nativa en Kotlin con Jetpack Compose para vendedores en campo.

Se desplegaron en contenedores Docker orquestados mediante Docker Compose y Traefik como reverse proxy. Cada servicio tiene su propio Dockerfile y compose con configuración específica de entornos (dev/prod) y path prefixes para routing.

Vista del sistema en producción

Dashboard principal del Cotizador Dashboard principal mostrando lista de cotizaciones activas con filtros y búsqueda.

Formulario de creación de cotización Interfaz de creación de cotización con selección de componentes (alternador, motor, transformador).

Detalle de cotización generada Vista de detalle de una cotización completa con precios, opcionales y condiciones comerciales.

Principales retos y cómo los resolvimos

  1. Calidad y heterogeneidad de los datos
  • Problema: los catálogos provenían de hojas Excel pivoteadas (alternadorpotencia.xlsx, motorderrateo.xlsx, alternadorderrateo.xlsx, integradora_nacional.xlsx, integradora_exportacion.xlsx) en formato legible para humanos pero incompatible con las estructuras de tablas MySQL. Incluían inconsistencias en nombres, unidades y códigos entre diferentes archivos.
  • Solución: cotizador-data-transformer implementó una pipeline FastAPI con Pandas: detección automática de encoding (chardet), normalización de claves, deduplicación por heurísticas (SKU + atributos), y transformación a formato relacional. Se añadió RQ (Redis Queue) para procesamiento asíncrono con rq-dashboard para monitoreo y WebSockets para notificar progreso en tiempo real al frontend. El sistema genera reportes de filas fallidas con sugerencias de corrección.
  1. Reglas de negocio complejas y configurables
  • Problema: reglas por cliente, segmento, país, y excepciones comerciales que afectan descuentos, recargos y configuraciones fiscales.
  • Solución: diseñamos un motor de reglas dentro del cotizador-backend con las siguientes características:
    • Regla compuesta por: condición (expresada en un DSL JSON limitado), prioridad y acción (fórmula o ajuste de precio).
    • Evaluación determinista con short-circuit y memoización de subexpresiones para rendimiento.
    • Capa de “versionado” de reglas para permitir rollback seguro y auditoría.
  1. Integración con SAP y latencia
  • Problema: consultas a SAP mediante WebServices SOAP (zws_consultar_cliente_v6, zws_crear_cliente_v6, zws_crear_pedido_QAS, zws_get_vendedores, zws_obtener_stock) pueden ser lentas y no es aceptable bloquear la experiencia de cotización.
  • Solución:
    • cotizador-sap-proxy actúa como adaptador PHP síncrono que maneja la complejidad SOAP y expone endpoints REST simples al backend Node.js/Bun.
    • Sincronización bidireccional de clientes: consultas y creación desde el cotizador hacia SAP.
    • Para consultas de stock en tiempo real, se implementó polling con feedback visual en el frontend.
    • Socket.io permite notificaciones push cuando operaciones largas (creación de pedidos, validaciones) completan, evitando polling constante del cliente.
  1. Rendimiento y escalado
  • Problema: garantizar latencias bajas bajo concurrencia y picos estacionales, con más de 50 tablas relacionadas en MySQL.
  • Solución técnica:
    • Connection pooling de MySQL2 para reutilizar conexiones y evitar overhead.
    • Express-rate-limit para control de tráfico y prevención de abusos.
    • Bun runtime en lugar de Node.js por su velocidad de arranque superior y menor uso de memoria.
    • Path mapping con alias (#libs, #controllers, #models, etc.) para optimizar imports y reducir overhead de resolución de módulos.
    • Socket.io con health checks para monitorear estado del servidor en tiempo real.
    • Winston logging estructurado con niveles (Error, Warn, Info, Debug) y rotación de archivos para análisis post-mortem.
    • AWS S3 para almacenamiento de archivos (uploads, PDFs generados) evitando saturar el filesystem local.
  1. Consistencia y despliegues coordinados
  • Problema: cambios en catálogos, transformaciones y reglas de negocio requieren despliegues coordinados entre 4 servicios (frontend, backend, transformer, SAP proxy) sin romper cotizaciones activas.
  • Solución:
    • Dockerfiles multi-stage con separación dev/prod (Dockerfile.dev y Dockerfile).
    • Docker Compose per-service con variables de entorno específicas y configuración de Traefik para routing con path prefixes (/proyectos/cotizador, /proyectos/cotizador-api).
    • Networking compartido (dokploy-network) para comunicación inter-servicios.
    • GitHub Actions workflows en cada subproyecto (.github/) para CI/CD automatizado.
    • Vitest para tests unitarios en backend y frontend, supertest para tests de integración HTTP.
    • Environment-based configuration con archivos .env.example documentados.

Decisiones de tecnología destacadas

  • Lenguajes:
    • Bun (JavaScript runtime) para el backend por velocidad de arranque ~3x más rápida que Node.js y menor uso de memoria.
    • React 18.3 + Vite para frontend por hot-reload instantáneo y build optimizado.
    • Python 3.13 con FastAPI para ETL por su ecosistema de data wrangling (Pandas, openpyxl, chardet).
    • PHP para proxy SAP por compatibilidad nativa con SOAP y familiaridad del equipo con infra legacy.
    • Kotlin + Jetpack Compose para app Android por UI declarativa moderna y target SDK 35.
  • Comunicación:
    • REST con Express.js para rutas síncronas (28 controladores especializados).
    • Socket.io para notificaciones en tiempo real (progreso de imports, alertas, health checks).
    • SOAP WebServices para integración SAP (5 WSDLs principales).
  • Persistencia:
    • MySQL con connection pooling para datos transaccionales (50+ tablas).
    • AWS S3 para archivos (uploads, PDFs, documentos adjuntos).
    • RQ (Redis Queue) para jobs asíncronos en data-transformer con rq-dashboard para monitoreo.
  • UI/UX:
    • PrimeReact para componentes empresariales (tablas, formularios, modales).
    • TailwindCSS para styling custom y responsive.
    • React-PDF y Chart.js para generación de reportes visuales.
    • React Hook Form + Zod para validación de formularios type-safe.

Integración de los subproyectos (cómo los juntamos)

El proceso de integración siguió pasos concretos:

  1. Arquitectura modular por capas. En el backend definimos estructura MVC con separación clara: config/, controllers/, routes/, models/, middleware/, helpers/, libs/. Path mapping con imports alias (#libs, #controllers, #models, etc.) para mantener imports limpios y evitar hell de rutas relativas.

  2. Pipeline de importación de catálogos. cotizador-data-transformer expone endpoints FastAPI que reciben archivos Excel, los procesa con Pandas, y carga directamente a MySQL. RQ (Redis Queue) gestiona jobs asíncronos y notifica progreso via WebSockets. El frontend muestra barra de progreso en tiempo real durante imports largos.

  3. Integración SAP via proxy. cotizador-sap-proxy expone API REST simple que internamente maneja complejidad SOAP. El backend hace llamadas HTTP estándar al proxy, que traduce a SOAP, consulta SAP, y devuelve JSON. Esto aisló la lógica SOAP legacy del código moderno Node.js/Bun.

  4. Orquestación Docker + Traefik. Cada servicio tiene su compose.yml y compose.dev.yml con labels de Traefik para routing automático. Traefik detecta servicios por labels y configura rutas (PathPrefix(/proyectos/cotizador) para frontend, /proyectos/cotizador-api para backend). Networking compartido (dokploy-network) permite comunicación inter-servicios sin exponer puertos externos.

  5. Comunicación en tiempo real. Socket.io configurado tanto en backend (servidor) como frontend (cliente). Usado para: notificaciones de imports completados, alertas de stock bajo, health checks de servicios, y sincronización de cotizaciones colaborativas entre vendedores.

Ejemplos visuales de integración

Importación de catálogo con progreso en tiempo real Pipeline de importación mostrando barra de progreso en tiempo real via WebSockets mientras procesa Excel con RQ.

Configuración de grupo electrógeno Interfaz de configuración de producto mostrando selección de alternador, motor, transformador y opcionales.

Fichas técnicas públicas Interfaz de fichas técnicas mostrando información detallada de productos y configuraciones.

Diagramas de arquitectura

A continuación hay dos diagramas Mermaid que ilustran la arquitectura de componentes y el flujo de una cotización. Si tu generador de sitio no soporta Mermaid, dime y puedo generar PNGs y agregarlos a la carpeta del post.

Arquitectura general

Arquitectura general Diagrama de arquitectura general del sistema.

Flujo de una cotización (detalle)

Flujo de una cotización Diagrama de flujo de una cotización detallando la interacción entre componentes.

Ejemplo técnico: flujo de una cotización

  1. Frontend envía POST /api/cotizar con parámetros (cliente, productos, opciones de configuración).
  2. Backend valida request usando Zod schemas y obtiene connection del pool MySQL2.
  3. Backend ejecuta queries para obtener catálogos relacionados (alternador, motor, transformador, cables según configuración).
  4. Lógica de pricing ejecutada en controladores especializados (controllers/quotes.js) que calculan precio base + opcionales + condiciones comerciales.
  5. Si requiere validación SAP (stock, cliente nuevo), backend hace HTTP request a cotizador-sap-proxy que traduce a SOAP y consulta SAP.
  6. Resultado se persiste en tablas cotizacion y cotizacion_detalle, además de registro en bitacora para auditoría.
  7. Backend emite evento Socket.io ‘cotizacion_creada’ para notificar a otros clientes conectados.
  8. Frontend recibe respuesta JSON con cotización completa, metadata de trazabilidad (versiones, usuario, timestamp) y opción para generar PDF vía React-PDF.

Pruebas, calidad y observabilidad

  • Tests:
    • Vitest para unitarios en backend (test/) y frontend (coverage/).
    • Supertest para tests de integración HTTP de endpoints REST.
    • Cypress para E2E del frontend (cypress/).
    • Pytest + pytest-cov para data-transformer con coverage tracking.
  • Observabilidad:
    • Winston logging estructurado con niveles y rotación de archivos.
    • Socket.io health checks para monitoreo de disponibilidad en tiempo real.
    • Morgan para logging HTTP requests en desarrollo.
    • Biome para linting y formatting consistente (biome.json).
    • RQ Dashboard para visualizar jobs asíncronos y failures en transformer.
  • Seguridad:
    • JWT tokens (jsonwebtoken) para autenticación stateless.
    • Bcrypt para hashing de passwords.
    • CORS configurado para dominios específicos.
    • Express-rate-limit para prevención de abusos.
    • Multer con validación de tipos de archivo para uploads seguros.
    • Prepared statements en MySQL2 para prevenir SQL injection.

Lecciones aprendidas

  • Bun sobre Node.js: El switch a Bun runtime redujo tiempos de cold start ~60% y memoria ~30%, critical para contenedores efímeros.
  • Path mapping salva vidas: Imports con alias (#libs, #controllers) eliminaron el hell de rutas relativas (../../../) y mejoraron DX significativamente.
  • Proxy SOAP simplifica integración: Aislar lógica SOAP en PHP proxy permitió que el equipo Node.js trabajara con REST estándar sin lidiar con XML/WSDL.
  • Socket.io para UX proactiva: Notificaciones push eliminaron necesidad de polling y mejoraron perceived performance.
  • Docker Compose + Traefik = DX++: Routing automático por labels eliminó configuración manual de nginx y permitió deploy de servicios independientes sin coordinación.
  • Arquitectura modular por capas: 28 controladores especializados mantienen separation of concerns y facilitan testing unitario.
  • RQ para jobs pesados: Procesar Excels grandes de forma asíncrona con feedback en tiempo real eliminó timeouts HTTP y mejoró UX.
  • MySQL connection pooling: Reutilizar conexiones redujo overhead ~40% en queries frecuentes vs crear conexión por request.
  • Tablas de auditoría desde día 1: La tabla bitacora salvó debugging sessions incontables y proveyó compliance para auditorías comerciales.

Conclusión

El “Cotizador de Modasa” demuestra cómo integrar tecnologías heterogéneas (React, Bun/Express, Python FastAPI, PHP SOAP, Android Kotlin) para resolver un dominio complejo: cotización de grupos electrógenos con 50+ tablas relacionadas, integración SAP legacy, y transformación de datos sucios de Excel.

La arquitectura resultante es:

  • Modular: 4 servicios independientes con responsabilidades claras (frontend, backend, transformer, proxy).
  • Escalable: Containerización con Docker + Traefik permite escalar servicios independientemente.
  • Observable: Winston, Socket.io health checks, RQ Dashboard, y bitácora proveen visibilidad end-to-end.
  • Mantenible: 28 controladores especializados, path mapping, y arquitectura por capas facilitan onboarding y debugging.
  • Auditable: Tabla bitácora y tracking completo de cambios cumplen requerimientos comerciales y de compliance.

Técnicamente, las decisiones más impactantes fueron: Bun runtime (performance), Socket.io (UX proactiva), PHP proxy (aislamiento SOAP), RQ (jobs asíncronos), y Traefik (orquestación simplificada).

El resultado: una plataforma que procesa cotizaciones en <300ms, soporta concurrencia de 200+ usuarios simultáneos, y mantiene sincronización bidireccional con SAP sin bloquear la experiencia del usuario.


Reflexión personal: lecciones desde las trincheras

El problema: Los requerimientos cambiaron constantemente durante el desarrollo. Features que parecían simples (consulta SAP, PDFs básicos, imports) se volvieron complejos (sincronización bidireccional, fórmulas dinámicas, validaciones en tiempo real). La arquitectura inicial no anticipó esta complejidad.

El infierno de SAP: Integrar con servicios SOAP sin documentación real fue el mayor dolor. Los WSDLs eran esqueletos sin contexto. El verdadero problema: estructuras de datos incompatibles entre sistemas que funcionan en paralelo.

  • Cotizador → SAP: 50+ tablas MySQL normalizadas → XML jerárquico SAP con códigos opacos.
  • SAP → Cotizador: Fechas (dd.mm.yyyy vs ISO), números (1.234,56 vs 1234.56), códigos que cambiaban entre ambientes, campos opcionales inconsistentes (vacío/null/ausente).

Consecuencias:

  • Errores crípticos imposibles de debuggear (“Material no válido para sociedad 2000”).
  • Transformaciones frágiles en el PHP proxy que rompían con cada cambio SAP.
  • Testing limitado (no podíamos testear contra SAP real).
  • WSDLs que cambiaban de v5 a v6 sin aviso.

Lo que funcionó:

  • Schema validation con Zod para detectar respuestas malformadas.
  • Logging exhaustivo (Winston + XML completo SOAP) para debugging post-mortem.
  • Capa de adaptadores para normalizar formatos inconsistentes.
  • Retry logic con exponential backoff (muchos errores SAP eran transitorios).

Lecciones clave:

  1. Diseña para cambio, no para el presente. Capas de abstracción, interfaces claras, y arquitectura modular salvaron el proyecto. Los 28 controladores especializados permitieron agregar features sin refactors masivos.

  2. Sistemas legacy son cajas negras hostiles. Cuando integres con SAP/ERPs antiguos: asume que las estructuras mienten, loguea TODO, duplica estimados de tiempo, y mantén canal directo con el equipo legacy.

  3. Feature flags > deploys coordinados. Cada feature debió estar detrás de un flag desde día 1.

  4. Contract testing para integraciones SOAP. Captura XMLs reales (success + errors) como fixtures. Los mocks nunca reflejan la realidad de SAP.

  5. Documentación desde sprint 1. ARCH.md llegó tarde. ADRs (Architecture Decision Records) debieron existir desde el inicio, especialmente para mapeos SAP.

Moraleja: Cuando un stakeholder dice “solo necesitamos X simple”, traduce a “eventualmente necesitaremos X+Y+Z complejo”. Y cuando te digan “SAP tiene documentación”, duplica tu estimado. Los WSDLs mienten, los ambientes difieren, y los errores son crípticos.


Stack completo:

  • Frontend: React 18.3 + Vite + TailwindCSS + PrimeReact + TanStack Query + Socket.io client
  • Backend: Express.js + Bun runtime + MySQL2 + Socket.io + JWT + Winston + AWS S3
  • ETL: Python 3.13 + FastAPI + Pandas + RQ (Redis Queue) + WebSockets
  • Proxy: PHP + SOAP + REST adapter
  • Mobile: Kotlin + Jetpack Compose + Android SDK 35
  • Infra: Docker + Docker Compose + Traefik + GitHub Actions