← Blog técnico

Arquitectura de aplicaciones

Backend modular: cómo diseñar una app interna mantenible sin sobrediseñar

Una app interna rara vez falla porque le falte un framework. Se vuelve costosa cuando reglas, consultas, permisos e integraciones quedan mezclados y cada cambio exige tocar todo.

Node.jsTypeScriptPostgreSQLPrisma

En una primera versión es razonable avanzar con pocas piezas. El riesgo aparece cuando la velocidad inicial se convierte en una arquitectura accidental: controladores que deciden todo, consultas duplicadas, estados escritos como texto libre y automatizaciones que actualizan datos sin pasar por las mismas reglas.

La alternativa no exige microservicios. Para muchos equipos, un monolito modular bien diseñado ofrece un límite claro entre dominios, una sola operación desplegable y suficiente disciplina para crecer.

1) Modular no significa repartir archivos por tipo

Separar carpetas en controllers, services y models ordena el código, pero no define responsabilidades de negocio. Un módulo útil representa una capacidad: solicitudes, aprobaciones, facturación, inventario o identidad.

Cada módulo debe exponer casos de uso deliberados y proteger sus invariantes. Otros módulos no deberían modificar directamente sus tablas sólo porque comparten PostgreSQL.

2) Cuatro capas con responsabilidades verificables

CapaResponsabilidadSeñal de alerta
Interfaz / APIAutenticar, validar formato y traducir la solicitudContiene decisiones financieras u operativas
AplicaciónCoordinar el caso de uso y su transacciónDepende de detalles de HTTP o de la pantalla
DominioEstados, reglas e invariantesLas reglas viven sólo en formularios
InfraestructuraPersistencia, correo, archivos y APIs externasUn proveedor dicta el modelo de negocio

3) La base de datos también protege el proceso

TypeScript reduce errores durante el desarrollo; no evita dos escrituras concurrentes ni datos insertados por otro canal. Claves foráneas, restricciones únicas, tipos adecuados y transacciones deben respaldar las reglas que no pueden romperse.

Regla práctica: si una inconsistencia afectaría dinero, permisos, inventario o auditoría, no debe depender exclusivamente de una validación en la interfaz.

Prisma facilita acceso tipado y migraciones, pero el esquema sigue siendo una decisión de producto. Conviene revisar índices, cardinalidad, bloqueos y planes de migración en vez de tratar el ORM como sustituto del diseño de datos.

4) Modelar comandos y consultas por separado

Una pantalla operativa puede requerir una consulta agregada y optimizada; aprobar una solicitud requiere validar permisos, estado vigente y reglas de transición. Forzar ambos recorridos a compartir el mismo objeto produce acoplamiento innecesario.

No hace falta implementar CQRS completo. Basta con distinguir lecturas orientadas a vistas de comandos que cambian estado. Así se optimizan reportes sin debilitar las reglas de escritura.

5) Transacciones cortas; efectos externos después

Enviar correo o invocar una API dentro de una transacción mantiene bloqueos mientras se espera a otro sistema. Una opción más robusta es guardar el cambio y un evento pendiente en la misma transacción; un worker procesa después la notificación con idempotencia y reintentos.

n8n puede orquestar tareas externas cuando aporta visibilidad operativa. El backend conserva la autoridad sobre estados y reglas; el workflow recibe eventos y devuelve resultados mediante contratos explícitos.

6) Permisos ligados a acciones, no sólo a pantallas

Ocultar un botón mejora la experiencia, pero no es control de acceso. El backend debe autorizar cada comando según identidad, rol, alcance y estado del registro. Acciones sensibles necesitan registrar actor, momento, motivo y valores relevantes antes y después.

7) Contratos y errores que ayuden a operar

Una API mantenible usa esquemas versionables, códigos de error estables y un identificador de correlación. El usuario necesita saber qué corregir; soporte necesita encontrar el recorrido; desarrollo necesita contexto técnico sin exponer secretos.

  • Errores de validación con campos y reglas comprensibles.
  • Conflictos de versión cuando otro usuario ya modificó el registro.
  • Resultados idempotentes para comandos repetidos.
  • Logs estructurados conectados con caso, evento y actor.

8) Probar fronteras de riesgo

La cobertura total es menos útil que pruebas deliberadas sobre invariantes: transiciones inválidas, concurrencia, permisos, redondeos, duplicados y reintentos. Las integraciones externas necesitan pruebas de contrato y escenarios de timeout o respuesta parcial.

9) Un camino incremental

  1. Definir módulos a partir de capacidades y responsables del proceso.
  2. Elegir un flujo crítico y escribir estados, invariantes y permisos.
  3. Encapsular sus casos de uso antes de reorganizar todo el sistema.
  4. Respaldar reglas críticas con restricciones y transacciones.
  5. Extraer efectos externos a una cola o patrón outbox.
  6. Medir errores, tiempos y excepciones antes de dividir despliegues.

10) Mantenibilidad es capacidad de cambiar con control

Una buena arquitectura interna no intenta anticipar cada requisito. Hace visible dónde vive una regla, limita el impacto de modificarla y deja evidencia cuando el proceso cambia.

Node.js, TypeScript, PostgreSQL y Prisma forman una base productiva, pero la ventaja aparece cuando la estructura técnica refleja responsabilidades operativas reales. Ese criterio permite crecer sin convertir cada nueva pantalla en deuda futura.

¿Su aplicación interna crece, pero cada cambio toca demasiadas piezas?

Podemos revisar el proceso, los límites del dominio y la arquitectura de una aplicación web empresarial para definir un siguiente incremento controlable.

Revisar la arquitectura