smplx.
Arquitectura Shopify

Evitar la deuda técnica en Shopify: Principios de arquitectura [2026]

Claudio Gerlich··12 min

En nuestra Guía de Arquitectura Shopify hablamos sobre buena arquitectura. Hoy: La otra cara de la moneda. La deuda técnica.

En smplx. hemos visto tiendas que después de 3 años ya no se podían modificar. No por la tecnología. Por la deuda que se había acumulado.

La buena noticia: Es evitable. Y si ya tienes deuda, se puede reducir.

¿Qué es la deuda técnica?

Definición: Código/arquitectura que se construyó rápidamente pero es difícil de cambiar.

Metáfora: Pides un préstamo rápido en el banco. Ahora pagas intereses. Para una tienda Shopify, los "intereses" son cada funcionalidad que se vuelve más difícil de implementar.

Cómo se acumula la deuda

  1. Presión de tiempo: "¡Necesitamos esta función para el viernes!" -> Solución rápida y sucia -> Mañana un bug -> Parche encima -> Después de 6 meses: Imposible de mantener

  2. Proliferación de apps: "Instalemos una app para eso" -> Primera app para descuentos -> Segunda app para paquetes -> Tercera app para variantes -> Quinta app después: Tienes 5 apps, nadie sabe cómo se conectan

  3. Código sin documentar: "Yo sé cómo funciona" -> El desarrollador deja la empresa -> Nuevo desarrollador: "¿Por qué este código hace eso?" -> No puede cambiar nada de forma segura -> No hay nuevas funcionalidades

  4. Temas desactualizados: "Mantengamos el tema viejo" -> Versión antigua del tema -> Nuevas funcionalidades no son posibles -> Se pierden actualizaciones de seguridad -> Después de 2 años: Ruptura total

  5. Código Liquid espagueti

<!-- INCORRECTO: Este código realmente existe -->
{% if product.handle == "special-product-1" %}
  <div class="special-styling-1">
    {% if customer.email == "vip@customer.com" %}
      <span class="vip-text">VIP Only!</span>
    {% else %}
      <!-- Old code from 2019 -->
      {% for item in cart.items %}
        {% if item.product.handle == "special-product-1" %}
          <!-- Recursive logic - code smell! -->
        {% endif %}
      {% endfor %}
    {% endif %}
  </div>
{% elsif product.handle == "special-product-2" %}
  <!-- 200 Lines of duplicate code -->
{% elsif product.handle == "special-product-3" %}
  <!-- 200 Lines of duplicate code -->
{% endif %}
<!-- This repeats 50 times! -->

Caso real: La historia de Bekateq

Bekateq tenía una situación que muchos conocen:

Punto de partida (Antes):

  • 5 apps personalizadas diferentes
  • 30+ snippets del tema con lógica espagueti
  • Sin tests
  • Difícil de entender
  • Las nuevas funcionalidades tardaban semanas
  • Las correcciones de bugs generaban nuevos bugs

El coste:

  • Mensual: 2 desarrolladores
  • Mensual: 1-2 bugs por semana
  • Mensual: Las funcionalidades tardan 3 veces más

La solución: Una app para gobernarlas a todas

Bekateq escribió una única app personalizada con 4.400 líneas de código y cero dependencias externas (excepto el SDK de Shopify).

El resultado (Después):

  • 1 app en lugar de 5
  • Comprensible + documentada
  • Tests posibles
  • Nuevas funcionalidades: 5 días en lugar de 15
  • Bugs: Casi cero

La inversión:

  • 8 semanas de refactorización
  • Coste: 50-80k EUR
  • ROI: 6 meses (desarrollo más rápido)

Aprendizaje clave: El tiempo que ahorras se paga solo.

Señales de que tienes deuda

Señal 1: "No, no puedo cambiar eso"

El desarrollador dice: "No puedo construir esa función,
                 porque no puedo tocar esa parte del código."

Eso es deuda. Tu código se está bloqueando a sí mismo.

Señal 2: Los cambios tardan cada vez más

Métrica: La velocidad disminuye

Mes 1: 10 funcionalidades
Mes 3: 8 funcionalidades
Mes 6: 5 funcionalidades
Mes 12: 2 funcionalidades

Esos son intereses de la deuda. Cuanto más viejo el código, más "intereses".

Señal 3: Los tests son imposibles

"No puedo testear este código porque..."
- "...todo es global"
- "...demasiadas dependencias"
- "...miles de sentencias if"

Si el código no se puede testear, es deuda.

Señal 4: Cambios pequeños, bugs grandes

Cambio: Texto del botón
Resultado: El carrito ya no funciona

Eso es deuda. Acoplamiento fuerte.

Señal 5: 5+ apps para cosas simples

- App 1: Descuentos
- App 2: Variantes
- App 3: Paquetes
- App 4: Reseñas
- App 5: Inventario
- App 6: Analítica

Cada app tiene:

  • Sus propios límites de API
  • Sus propios webhooks
  • Sus propios rate limits
  • Potenciales conflictos

Esa es la deuda de la "proliferación de apps".

Cómo evitar la deuda (Arquitectura primero)

Principio 1: Decide pronto — "Construir vs. comprar"

Pregunta: ¿Necesitamos una app personalizada para esto?

Árbol de decisión:

¿Se necesita la función X?
├─ ¿Se puede hacer con Shopify estándar?
│  └─ SÍ -> Usa lo estándar (¡sin deuda!)
│
├─ ¿Se necesita código personalizado?
│  └─ SÍ -> ¿Nativo (Tema) o App?
│     ├─ ¿Tema? (Lógica UI simple)
│     │  └─ Sí -> Liquid (rápido, sin overhead)
│     │
│     └─ ¿App? (Lógica compleja, Admin API)
│        └─ Una app con propósito claro
│
└─ ¿Se puede hacer con una app existente?
   └─ SÍ -> Usa la App (¡no personalizado!)

Ejemplo: Sistema de descuentos

Antes: 3 apps diferentes
   - Discount App A
   - Discount App B
   - Custom Discount App

Mejor: El propio sistema de descuentos de Shopify
   (¡Sin código personalizado necesario!)

Si se necesita personalización: 1 app, propósito claro

Principio 2: Escribe código para otros

Suposición: Tu código será gestionado por otra persona en 6 meses.

Concretamente:

// INCORRECTO: Solo tú lo entiendes
const x = arr.map(a => a.p * (1 - d)).filter(b => b > 0);

// CORRECTO: Todos lo entienden
const applyDiscount = (price, discountPercent) => {
  return price * (1 - discountPercent / 100);
};

const filteredPrices = prices
  .map(price => applyDiscount(price, discount))
  .filter(price => price > 0);

En contexto Shopify (Liquid):

<!-- INCORRECTO: Números mágicos -->
{% if product.price > 10000 %}
  <span class="premium">Premium</span>
{% endif %}

<!-- CORRECTO: Documentado -->
{% comment %}
Premium products are those with price > $100 (10000 cents)
They get special treatment in search and collections
{% endcomment %}

{% assign premium_price_threshold = 10000 %}
{% if product.price > premium_price_threshold %}
  <span class="premium">Premium</span>
{% endif %}

Principio 3: Una responsabilidad por app/componente

El principio de responsabilidad única (SRP)

Incorrecto:

MyAwesomeApp gestiona:
- Descuentos
- Paquetes
- Inventario
- Analítica
- Checkout personalizado

¡Cada cambio afecta a todo!

Correcto:

DiscountApp gestiona:
- Crear descuentos
- Aplicar descuentos
- Informes de descuentos

(¡Solo eso!)

Principio 4: Documenta las decisiones

# Decision Log

## Why do we use Metafields for Product Bundles?
**Date:** 2025-01-15
**Decision:** Use Metaobjects instead of separate App

**Alternatives considered:**
1. Custom App - Too heavyweight for this feature
2. Liquid only - Can't handle admin experience well

**Why Metaobjects?**
- Native Shopify (no external dependencies)
- Queryable in GraphQL (good DX)
- Admin UI auto-generated
- Can be related to Products

**Trade-offs:**
- Requires Shopify Plus (but we have it)
- Limited by Metaobject rate limits (ok for our use case)

**Who decided?** Claudio + Team Lead
**Status?** Implemented

Este registro ayuda a los nuevos desarrolladores: "¿Por qué no se hizo de otra manera?"

Principio 5: Escribe tests (¡pronto!)

No al final. Desde el principio.

// test-discount-logic.js
import { applyDiscount, validateCoupon } from './discount.js';

describe('Discount Logic', () => {
  it('should apply 10% discount correctly', () => {
    expect(applyDiscount(100, 10)).toBe(90);
  });

  it('should not allow negative discounts', () => {
    expect(() => applyDiscount(100, -10)).toThrow();
  });

  it('should validate coupon expiry', () => {
    const expiredCoupon = { code: 'OLD', expiresAt: '2024-01-01' };
    expect(validateCoupon(expiredCoupon)).toBe(false);
  });
});

Cuando existen tests, puedes refactorizar de forma segura.

Reducir la deuda existente

Paso 1: Auditoría (Obtener los hechos)

# Medir complejidad del código
npm install complexity-report
complexity-report --format markdown . > complexity.md

# Encontrar código sin usar
npm install unused-exports
unused-exports

# Verificar dependencias
npm audit

# Cobertura de tests
npm test -- --coverage

Resultado:

  • ¿Qué archivos tienen más deuda?
  • ¿Qué funciones son demasiado complejas?
  • ¿Qué dependencias están desactualizadas?

Paso 2: Priorizar (¿Qué te frena más?)

Pregunta: Si pudieras refactorizar un archivo, ¿cuál haría que las funcionalidades se desarrollen más rápido?

Ejemplo: Bekateq

Priorizado por: Impacto en la velocidad
1. Discount Logic (20% más compleja de lo esperado)
2. Variant Handling (10 patrones diferentes)
3. Inventory Sync (5 fuentes diferentes)

Paso 3: Refactorizar con seguridad

¡No simplemente reescribir!

// Refactorización SEGURA:

// Step 1: Write tests (para el código antiguo!)
// Step 2: Refactor (con tests en verde)
// Step 3: Deploy (los tests te protegen)
// Step 4: Monitor (¿sigue funcionando?)
// Step 5: Delete old code (cuando todo esté ok)

Ejemplo: Refactorización de Discount Logic

Antes:
├─ discount.liquid (200 líneas de espagueti)
├─ discount.js (300 líneas)
└─ discount.scss (100 líneas)

Proceso:
├─ Paso 1: Escribir tests para el código antiguo
├─ Paso 2: Extraer en funciones
├─ Paso 3: Deploy (los tests protegen)
├─ Paso 4: Más extracciones
├─ Paso 5: Deploy
├─ Paso 6: Eliminar código antiguo

Después:
├─ discount-logic.js (100 líneas, testeable)
├─ discount-component.jsx (50 líneas)
└─ discount.scss (50 líneas)

Coste-Beneficio:

Refactorización: 2 semanas
Ahorro: 1 día por nueva funcionalidad (para siempre)
ROI: Punto de equilibrio a los 3 meses

Paso 4: Monitoreo (Volverás a acumular deuda)

Métricas para la salud del código:

- Code Coverage > 80%
- Complexity Score < 10 por función
- Sin dependencias desactualizadas
- Cero vulnerabilidades de seguridad

Mejores prácticas de smplx.

1. Revisión de arquitectura (antes de cada función importante)

Checklist:

  • ¿Encaja con nuestra arquitectura existente?
  • ¿Necesitamos una nueva app o Liquid?
  • ¿Cuáles son los riesgos?
  • ¿Cómo lo testeamos?
  • ¿Quién lo mantiene?

2. Code Review (antes de cada deploy)

## Review Checklist

- [ ] ¿Tests en verde?
- [ ] ¿Sin errores de linting?
- [ ] ¿Impacto en rendimiento aceptable?
- [ ] ¿Revisión de seguridad?
- [ ] ¿Documentación actualizada?
- [ ] ¿Código antiguo eliminado?

3. Evaluación trimestral de la deuda

# Q1 2026 Technical Debt Report

## Deuda: Alta
- El tema tiene 2 años (necesario Shopify 11 -> 12)
- Discount Logic demasiado compleja (20+ patrones)
- Sin tests para la integración con Admin API

## Inversiones necesarias:
1. Actualización del tema (2 semanas)
2. Refactorizar descuentos (3 semanas)
3. Escribir tests (2 semanas)

## Coste-Beneficio:
- Inversión: 150 horas de desarrollo
- Ahorro: ~10 horas al mes (nuevas funcionalidades)
- ROI: 15 meses

La realidad: La deuda está bien

Importante: ¡No tienes que estar libre de deuda!

La deuda está bien cuando:

  • Es intencional (sabes de ella)
  • Es limitada en el tiempo (plan para reducirla)
  • No te bloquea (las funcionalidades siguen funcionando)

La deuda es crítica cuando:

  • Está oculta (nadie conoce el código)
  • No tiene estrategia de salida
  • Bloquea nuevas funcionalidades
  • Causa bugs constantemente

Checklist: Prevención de deuda

  • Decisiones documentadas
  • Una App = Un propósito
  • Tests presentes
  • Código legible
  • No más de 5 apps para cosas simples
  • Tema actualizado (< 1 año)
  • Dependencias actualizadas
  • Code review antes de deploy
  • Evaluación trimestral de deuda
  • Plan de refactorización claro

Sobre el autor

Claudio Gerlich es Technical Architect en smplx. y está especializado en arquitectura Shopify desde 2020. Con Bekateq demostramos: La deuda se puede reducir, y merece la pena.

La deuda técnica no es una promesa de funcionalidades. Es tu futuro vendido por anticipado.

smplx. es Shopify Technical Partner (desde 2020) con sede en Muensterland, NRW.