He configurado integraciones de Stripe probablemente una docena de veces en los últimos años, y aquí está lo que he aprendido: el tutorial de inicio es la parte fácil. La parte difícil es ejecutar cuatro modelos de precios completamente diferentes en la misma organización, manejar 30+ monedas de países sin errores de redondeo, y asegurarse de que tus controladores de webhook no fallen silenciosamente a las 3 AM un sábado.

Esta no es otra publicación "crea una sesión de pago en 5 minutos". Vamos a recorrer cuatro modelos de precios en producción que hemos construido y operado -- suscripciones escalonadas con precios regionales, comisiones de marketplace mediante Stripe Connect, donaciones recurrentes vinculadas a entidades específicas, y pagos de servicios únicos. Cada uno tiene su propio conjunto de trampas, y compartiré los patrones de código específicos y decisiones de configuración que nos salvaron de errores dolorosos.

Tabla de Contenidos

Mejor Configuración de Stripe para Negocio de Suscripción: 4 Modelos Que Ejecutamos

Por Qué Una Configuración de Stripe No Se Ajusta a Todos

La documentación de Stripe es excelente para negocios de modelo único. Eliges suscripciones o pagos únicos, sigues la guía, y estás en línea. Pero la mayoría de los negocios reales no permanecen tan simples durante mucho tiempo.

Operamos en múltiples productos: una plataforma SaaS con suscripciones escalonadas, un marketplace que toma comisiones de proveedores, una iniciativa benéfica con patrocinios recurrentes, y un sistema de reserva de consulta con pagos únicos. Cada uno de estos vive bajo la misma cuenta de Stripe pero requiere configuraciones fundamentalmente diferentes para productos, precios, webhooks, y gestión de clientes.

El error más grande que veo hacer a los equipos es intentar forzar toda su facturación en un modelo. Una arquitectura basada en suscripción se rompe cuando necesitas pagos únicos. Un enfoque de solo sesión de pago colapsa cuando necesitas facturación recurrente con prorrateo. Necesitas pensar sobre tu configuración de Stripe como una cartera de patrones de facturación.

Si estás construyendo algo similar -- especialmente en una arquitectura sin cabecera con Next.js o Astro en la interfaz -- los patrones aquí te ahorrarán semanas de depuración.

Modelo 1: Suscripciones Escalonadas con Precios Regionales

Este es el modelo más complejo que ejecutamos, y es el que nos enseñó las lecciones más dolorosas. La configuración: cuatro niveles (Gratuito, Básico, Pro, Premium) con precios que varían en 30+ países.

La Estructura de Producto en Stripe

En Stripe, cada nivel es un Producto separado. Cada Producto tiene múltiples Precios -- uno por combinación de moneda/región. Esto es importante: no intentes usar un único Precio y hacer conversión de moneda tú mismo. Los precios multimoneda de Stripe están propósitos diseñados para esto.

// Configuración de precios regionales
const REGIONAL_PRICING = {
  pro: {
    USD: { monthly: 2900, yearly: 29000 },  // $29/mes, $290/año
    EUR: { monthly: 2700, yearly: 27000 },  // €27/mes, €270/año
    GBP: { monthly: 2300, yearly: 23000 },  // £23/mes, £230/año
    JPY: { monthly: 4200, yearly: 42000 },  // ¥4,200/mes -- ¡NO ¥42.00!
    KRW: { monthly: 38000, yearly: 380000 }, // ₩38,000/mes
    INR: { monthly: 190000, yearly: 1900000 }, // ₹1,900/mes
    BRL: { monthly: 14900, yearly: 149000 }, // R$149/mes
  },
  // ... repetir para básico, premium
};

¿Notaste esos valores de JPY y KRW? Llegaré a ese error en detalle más tarde, pero la versión corta: estas son monedas sin decimales. Cuando pasas 4200 para JPY, Stripe lo interpreta como ¥4,200 -- no ¥42.00. Si multiplicas por 100 como lo haces para USD, acabas de cobrar a alguien ¥420,000 ($2,800) en lugar de ¥4,200 ($28). Pregúntame cómo lo sé.

Lógica de Prueba Regional

No todas las regiones obtienen una prueba gratuita. Aprendimos esto de la manera difícil con ciertos mercados del sudeste asiático donde el abuso de prueba fue significativamente mayor que otras regiones. Nuestra configuración se ve así:

const TRIAL_CONFIG = {
  default_trial_days: 14,
  excluded_regions: ['VN', 'PH', 'ID', 'TH', 'MM', 'KH', 'LA'],
  reduced_trial_regions: {
    IN: 7,
    BR: 7,
  },
};

function getTrialDays(countryCode) {
  if (TRIAL_CONFIG.excluded_regions.includes(countryCode)) {
    return 0;
  }
  return TRIAL_CONFIG.reduced_trial_regions[countryCode] 
    ?? TRIAL_CONFIG.default_trial_days;
}

Esto se pasa a la creación de suscripción:

const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: regionalPriceId }],
  trial_period_days: getTrialDays(customer.address.country),
  payment_behavior: 'default_incomplete',
  payment_settings: {
    save_default_payment_method: 'on_subscription',
  },
  expand: ['latest_invoice.payment_intent'],
});

Comportamiento de Prorrateo

Cuando alguien mejora de Básico a Pro a mitad del ciclo, necesitas decidir: ¿pagan la diferencia inmediatamente, o en el próximo ciclo de facturación? Usamos create_prorations con pago inmediato:

const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
  items: [{
    id: existingItemId,
    price: newPriceId,
  }],
  proration_behavior: 'create_prorations',
  payment_behavior: 'pending_if_incomplete',
});

Para degradaciones, programamos el cambio para el final del período de facturación. Nadie quiere un cálculo de crédito sorpresa en su factura.

Portal del Cliente

El Portal del Cliente de Stripe está subestimado. En lugar de construir tu propia interfaz de gestión de suscripción, configura el portal y redirige a los usuarios allí:

const portalSession = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: `${process.env.APP_URL}/settings/billing`,
});

Configúralo en el Panel de Stripe para permitir cambios de plan, cancelación (con una encuesta de razón de cancelación -- los datos son oro), y actualizaciones de método de pago. Esto solo nos ahorró probablemente 40 horas de desarrollo de interfaz.

Modelo 2: Comisiones de Marketplace con Stripe Connect

Nuestro modelo de marketplace usa Stripe Connect para facilitar pagos entre clientes y proveedores de servicios. La plataforma toma una comisión en cada transacción. Esta es la configuración de Stripe Connect que la mayoría de los tutoriales pasan por alto.

Incorporación del Proveedor

Cada proveedor en el marketplace necesita una cuenta de Stripe Express. El flujo de incorporación crea la cuenta y los redirige al onboarding alojado de Stripe:

const account = await stripe.accounts.create({
  type: 'express',
  country: provider.country,
  email: provider.email,
  capabilities: {
    card_payments: { requested: true },
    transfers: { requested: true },
  },
  business_type: 'individual',
  metadata: {
    provider_id: provider.id,
    platform: 'fme',
  },
});

const accountLink = await stripe.accountLinks.create({
  account: account.id,
  refresh_url: `${process.env.APP_URL}/provider/onboarding/refresh`,
  return_url: `${process.env.APP_URL}/provider/onboarding/complete`,
  type: 'account_onboarding',
});

El detalle clave: refresh_url es donde Stripe envía a los usuarios si el enlace expira. Esto sucede más a menudo de lo que crees -- si alguien comienza el onboarding en su teléfono, se distrae, y regresa más tarde. Siempre maneja esto con gracia generando un nuevo enlace.

Estructura de Comisión

Cuando un cliente reserva un servicio, creamos un PaymentIntent con un application_fee_amount:

const paymentIntent = await stripe.paymentIntents.create({
  amount: bookingAmountInCents,
  currency: 'usd',
  application_fee_amount: Math.round(bookingAmountInCents * 0.15), // 15% de comisión de plataforma
  transfer_data: {
    destination: providerStripeAccountId,
  },
  metadata: {
    booking_id: booking.id,
    provider_id: provider.id,
    customer_id: customer.id,
  },
});

La comisión del 15% va a la plataforma. El 85% restante (menos la tarifa de procesamiento de Stripe) va a la cuenta Express del proveedor.

Programación de Pago

Por defecto, Stripe paga las cuentas Express de forma continua. Anulamos esto para pagos semanales, lo que nos da un búfer para reembolsos y disputas:

await stripe.accounts.update(providerStripeAccountId, {
  settings: {
    payouts: {
      schedule: {
        interval: 'weekly',
        weekly_anchor: 'friday',
      },
    },
  },
});

Los pagos del viernes significan que los proveedores ven dinero en sus cuentas bancarias el lunes. Es una cosa pequeña pero importa enormemente para la satisfacción y retención del proveedor.

Webhooks Específicos de Connect

Con Stripe Connect, recibes webhooks tanto para tu plataforma como para tus cuentas conectadas. Necesitas un endpoint de webhook separado para eventos de Connect:

// Endpoint de webhook regular
app.post('/webhooks/stripe', handlePlatformWebhooks);

// Endpoint de webhook de Connect
app.post('/webhooks/stripe-connect', handleConnectWebhooks);

El controlador de webhook de Connect necesita verificar el evento de manera diferente y verificar el campo account:

async function handleConnectWebhooks(req, res) {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers['stripe-signature'],
    process.env.STRIPE_CONNECT_WEBHOOK_SECRET // ¡Secreto diferente!
  );
  
  const connectedAccountId = event.account;
  // Ahora maneja el evento en contexto de la cuenta conectada
}

Mejor Configuración de Stripe para Negocio de Suscripción: 4 Modelos Que Ejecutamos - arquitectura

Modelo 3: Donaciones Recurrentes Vinculadas a Entidades

Este es para una iniciativa de caridad animal que estamos construyendo -- piensa en "patrocinar un animal específico" con donaciones mensuales recurrentes. El donante elige un animal, establece una cantidad mensual, y obtiene actualizaciones de fotos.

Suscripciones Vinculadas a Entidades

El truco aquí es vincular una suscripción de Stripe a una entidad específica (animal) en tu base de datos. Hacemos esto completamente a través de metadatos:

const subscription = await stripe.subscriptions.create({
  customer: donorCustomerId,
  items: [{
    price_data: {
      currency: 'usd',
      product: sponsorshipProductId,
      unit_amount: donorChosenAmount, // El donante elige su cantidad
      recurring: {
        interval: 'month',
      },
    },
  }],
  metadata: {
    entity_id: animal.id,
    entity_type: 'animal',
    entity_name: animal.name,
    sponsor_email: donor.email,
  },
});

Usar price_data en lugar de un Precio precreado permite que los donantes elijan su propia cantidad mensual. Esto es más limpio que crear cientos de objetos de Precio.

Correos de Actualización Mensual

Cuando invoice.paid se dispara para una suscripción de patrocinio, activamos el flujo de actualización mensual:

async function handleSponsorshipInvoicePaid(invoice) {
  const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
  const entityId = subscription.metadata.entity_id;
  
  // Encolar correo de actualización mensual con las fotos más recientes
  await emailQueue.add('sponsorship-update', {
    donorEmail: subscription.metadata.sponsor_email,
    entityId,
    invoiceAmount: invoice.amount_paid,
    invoicePdf: invoice.invoice_pdf,
  });
}

El correo incluye el PDF de factura (Stripe genera estos automáticamente), fotos recientes del animal patrocinado, y una actualización de cuidado. Es un toque pequeño que reduce dramáticamente el abandono en donaciones recurrentes.

Manejo de Cancelaciones de Donaciones

Cuando alguien cancela su patrocinio, necesitas manejarlo de manera diferente a una cancelación de SaaS. No hay "degradación" -- es cancelar o nada. Pero quieres que sea fácil volver a suscribirse más tarde:

async function handleSponsorshipCancellation(subscription) {
  const entityId = subscription.metadata.entity_id;
  
  // Marca el patrocinio como inactivo, no eliminado
  await db.sponsorships.update({
    where: { stripeSubscriptionId: subscription.id },
    data: { 
      status: 'inactive',
      cancelledAt: new Date(),
    },
  });
  
  // Enviar correo "te echaremos de menos" con enlace de re-suscripción fácil
  await sendCancellationEmail(subscription.metadata.sponsor_email, entityId);
}

Modelo 4: Pagos de Servicios Únicos

El modelo más simple, pero todavía hay detalles que importan. Este patrón es para reservas de consulta donde alguien paga una vez y obtiene un servicio -- sin facturación recurrente.

Sesión de Pago con Datos de Reserva

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{
    price: consultationPriceId,
    quantity: 1,
  }],
  customer_email: customer.email,
  metadata: {
    booking_id: booking.id,
    service_type: 'consultation',
    appointment_date: booking.date.toISOString(),
    practitioner_id: booking.practitionerId,
  },
  success_url: `${process.env.APP_URL}/booking/confirmed?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${process.env.APP_URL}/booking/${booking.id}`,
  expires_after: 1800, // 30 minutos
  payment_intent_data: {
    metadata: {
      booking_id: booking.id,
    },
  },
});

Dos cosas a tener en cuenta. Primero: expires_after previene que las sesiones de pago abandonadas se queden. Una ranura de reserva no debe sostenerse para siempre. Segundo: duplicamos el booking_id en payment_intent_data.metadata porque los metadatos de PaymentIntent son separados de los metadatos de Sesión de Pago. Cuando recibes el webhook payment_intent.succeeded, querrás ese ID de reserva justo allí.

Confirmación de Pago + Reserva

En checkout.session.completed, confirmamos la reserva y enviamos todo en un disparo:

async function handleCheckoutComplete(session) {
  const bookingId = session.metadata.booking_id;
  
  // Confirmar la reserva
  const booking = await db.bookings.update({
    where: { id: bookingId },
    data: { 
      status: 'confirmed',
      paymentSessionId: session.id,
      paidAt: new Date(),
    },
  });
  
  // Enviar confirmación al cliente
  await sendBookingConfirmation(session.customer_email, booking);
  
  // Notificar al practicante
  await notifyPractitioner(booking.practitionerId, booking);
}

Arquitectura de Webhook Que Realmente Funciona

En los cuatro modelos, los webhooks son la columna vertebral. Aquí está la arquitectura en la que nos hemos establecido después de demasiadas sesiones de depuración:

const WEBHOOK_HANDLERS = {
  'checkout.session.completed': handleCheckoutComplete,
  'invoice.paid': handleInvoicePaid,
  'invoice.payment_failed': handlePaymentFailed,
  'customer.subscription.created': handleSubscriptionCreated,
  'customer.subscription.updated': handleSubscriptionUpdated,
  'customer.subscription.deleted': handleSubscriptionDeleted,
  'account.updated': handleConnectAccountUpdated,
  'payment_intent.succeeded': handlePaymentSucceeded,
};

async function webhookHandler(req, res) {
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody, // Necesitas el cuerpo crudo, no JSON analizado
      req.headers['stripe-signature'],
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('La verificación de firma de webhook falló:', err.message);
    return res.status(400).send();
  }

  // Idempotencia: verifica si ya hemos procesado este evento
  const processed = await db.webhookEvents.findUnique({
    where: { stripeEventId: event.id },
  });
  if (processed) {
    return res.status(200).json({ received: true, duplicate: true });
  }

  const handler = WEBHOOK_HANDLERS[event.type];
  if (handler) {
    try {
      await handler(event.data.object, event);
      await db.webhookEvents.create({
        data: { stripeEventId: event.id, type: event.type, processedAt: new Date() },
      });
    } catch (err) {
      console.error(`Error procesando ${event.type}:`, err);
      return res.status(500).send(); // Stripe reintentará
    }
  }

  res.status(200).json({ received: true });
}

La verificación de idempotencia es crítica. Stripe reintentará webhooks fallidos, y absolutamente no quieres procesar el mismo evento dos veces -- especialmente para cosas como crear reservas o activar pagos.

Lógica de Reintentos de Pago Fallido y Dunning

Stripe tiene Smart Retries incorporados, pero deberías superponer tu propia lógica de dunning:

async function handlePaymentFailed(invoice) {
  const attemptCount = invoice.attempt_count;
  const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
  
  if (attemptCount === 1) {
    // Primer fallo: empujón suave
    await sendEmail(invoice.customer_email, 'payment-failed-soft', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
    });
  } else if (attemptCount === 2) {
    // Segundo fallo: más urgente
    await sendEmail(invoice.customer_email, 'payment-failed-urgent', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
      daysUntilCancellation: 7,
    });
  } else if (attemptCount >= 3) {
    // Advertencia final
    await sendEmail(invoice.customer_email, 'payment-failed-final', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
    });
  }
}

Configura el cronograma de reintentos de Stripe en Panel → Configuración → Suscripciones y correos → Administrar pagos fallidos. Usamos 3 reintentos en 14 días antes de la cancelación.

El Error de Moneda Sin Decimales Que Nos Costó Dinero

Esto merece su propia sección porque es un error que muerde a todos eventualmente. Stripe usa centavos (unidad de moneda más pequeña) para la mayoría de las monedas. $29.00 se convierte en 2900. Pero algunas monedas no tienen lugares decimales.

Aquí están las monedas sin decimales que importan:

Moneda Código Ejemplo: equivalente "$29" Lo que pasas a Stripe
Yen Japonés JPY ¥4,200 4200 (¡NO 420000!)
Won Coreano KRW ₩38,000 38000 (¡NO 3800000!)
Dong Vietnamita VND ₫700,000 700000
Peso Chileno CLP $25,000 25000
Guaraní Paraguayo PYG ₲200,000 200000

Aquí está la función de utilidad que usamos en todas partes:

const ZERO_DECIMAL_CURRENCIES = [
  'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW',
  'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF',
  'XOF', 'XPF',
];

function toStripeAmount(amount, currency) {
  const curr = currency.toUpperCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
    return Math.round(amount); // Ya en la unidad más pequeña
  }
  return Math.round(amount * 100);
}

function fromStripeAmount(stripeAmount, currency) {
  const curr = currency.toUpperCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
    return stripeAmount;
  }
  return stripeAmount / 100;
}

Usa esto en todas partes. En tu creación de pago, en tus controladores de webhook, en las pantallas de tu panel. En todas partes. La una vez que olvidas es la vez que cobras a alguien 100x lo que esperaban.

Comparación de los Cuatro Modelos

Aspecto Suscripciones Escalonadas Marketplace Donación Recurrente Pago Único
Producto de Stripe Múltiples Productos, múltiples Precios por producto Producto único por tipo de servicio Producto único, precios dinámicos Producto único, Precio fijo
Modo de Facturación subscription payment con Connect subscription payment
Complejidad de Webhook Alta (eventos del ciclo de vida) Alta (eventos de Connect) Media Baja
Manejo de Moneda Matriz de precios regional Moneda del proveedor Moneda del donante Moneda única
Soporte de Prueba Sí, dependiente de región N/A N/A N/A
Prorrateo Sí, en mejoras N/A N/A N/A
Complejidad de Reembolso Cálculos prorratados Reversión de comisión de plataforma Reembolso completo simple Reembolso completo simple
Portal del Cliente Esencial No necesario Agradable de tener No necesario
Tarifas de Stripe (2025) 2.9% + 30¢ 2.9% + 30¢ + 0.5% Connect 2.9% + 30¢ 2.9% + 30¢

Preguntas Frecuentes

¿Cuántos Productos de Stripe debo crear para precios escalonados? Un Producto por nivel. Entonces, si tienes Gratuito, Básico, Pro, y Premium, eso son cuatro Productos. Cada Producto entonces tiene múltiples Precios -- uno por moneda e intervalo de facturación. Un nivel Pro con facturación mensual y anual en 10 monedas significa 20 objetos de Precio en ese único Producto. Suena como mucho, pero Stripe lo maneja bien y mantiene tu catálogo organizado.

¿Puedo usar Stripe Checkout para suscripciones con precios regionales? Sí, pero necesitas determinar la región del cliente antes de crear la Sesión de Pago para que puedas pasar el ID de Precio correcto. Usamos geolocalización de IP (a través de encabezados de Cloudflare) para preseleccionar la moneda, luego permitimos que el cliente confirme o la cambie. No confíes en la moneda automática de Checkout -- quieres control sobre qué Precio ven.

¿Cuál es la diferencia entre cuentas Express y Custom de Stripe Connect? Las cuentas Express dejan que Stripe maneje el onboarding, la verificación de identidad, y el panel para tus proveedores. Las cuentas Custom te dan control total pero requieren que construyas todo eso tú mismo. Para la mayoría de los marketplaces, Express es la opción correcta. Nunca hemos tenido un caso donde la pérdida de control justificara el costo de ingeniería de Custom. Las cuentas Express también manejan la información de impuestos (1099s en EE.UU.) automáticamente, lo que es una victoria masiva de cumplimiento.

¿Cómo manejo los pagos de suscripción fallidos sin perder clientes? Superpón tres cosas: Smart Retries de Stripe (habilitados en Panel), correos de dunning personalizados activados por webhooks de invoice.payment_failed, y un período de gracia antes de la cancelación. Damos 14 días en 3 intentos de reintento. El primer correo es amable ("hey, tu tarjeta podría haber expirado"), el segundo es urgente, y el tercero es una advertencia final. Incluye un enlace directo al Portal del Cliente donde pueden actualizar su método de pago. Esto solo recupera alrededor del 30-40% de los pagos fallidos.

¿Necesito endpoints de webhook separados para Stripe Connect? Sí. Los eventos de plataforma y los eventos de cuentas de Connect usan diferentes secretos de webhook y estructuras de eventos diferentes. Los eventos de Connect incluyen un campo account que identifica qué cuenta conectada se relaciona con el evento. Registra dos endpoints en tu Panel de Stripe: uno para eventos de plataforma, uno para eventos de Connect. Esta separación también hace que la depuración sea mucho más fácil.

¿Qué son las monedas sin decimales y por qué debo preocuparme? Las monedas sin decimales como JPY (Yen Japonés) y KRW (Won Coreano) no usan unidades fraccionarias. Cuando Stripe dice "cantidad en la unidad de moneda más pequeña", para USD eso son centavos (2900 = $29.00), pero para JPY son yenes (4200 = ¥4,200). Si multiplicas por 100 como lo haces para USD, cobras ¥420,000 en lugar de ¥4,200. Siempre usa una función auxiliar que verifique la moneda antes de convertir. Stripe mantiene la lista oficial de monedas sin decimales en su documentación.

¿Debo usar el Portal del Cliente de Stripe Billing o construir el mío? Usa el Portal del Cliente para la gestión de suscripción a menos que tengas requisitos de interfaz muy específicos. Maneja cambios de plan, cancelaciones, actualizaciones de método de pago, e historial de facturas fuera de la caja. Puedes personalizar la marca y configurar qué acciones se permiten. Construir tu propio portal significa manejar cálculos de prorrateo, tokenización de método de pago, y flujos de SCA/3D Secure tú mismo. El portal es gratuito -- está incluido en tus tarifas de suscripción de Stripe.

¿Cómo pruebo precios regionales y manejo de moneda localmente? El modo de prueba de Stripe admite todas las monedas. Crea Precios de prueba en cada moneda que planees soportar, luego usa Stripe CLI para reenviar webhooks a tu servidor local: stripe listen --forward-to localhost:3000/webhooks/stripe. Para pruebas de moneda sin decimales específicamente, crea un Precio en JPY y verifica los montos en tus registros de controlador de webhook antes de ir en vivo. También mantenemos un conjunto de pruebas que ejecuta toStripeAmount y fromStripeAmount contra cada moneda soportada -- ha atrapado problemas más de una vez.

Si estás construyendo un producto basado en suscripción y necesitas ayuda con la arquitectura de facturación, o si estás integrando Stripe con una configuración de CMS sin cabecera, contáctanos. Hemos construido estos patrones en múltiples proyectos de CMS sin cabecera y podemos ayudarte a evitar los errores costosos. Consulta nuestra página de precios para modelos de compromiso -- hacemos tanto construcciones basadas en proyectos como asesoramiento continuo.