---
title: "Crea tu propio Parity Pricing para SaaS con Polar y Astro"
description: "Implementa precios de paridad de poder adquisitivo (PPP) con Polar y Astro: detecta el país del visitante en el servidor, mapéalo a un nivel de descuento y devuelve un % de descuento o un código de descuento de Polar para el checkout."
source: "https://www.launchfa.st/es/blog/parity-pricing-polar-astro"
author: "Rishi Raj Jain"
created_at: 2026-04-07T12:00:00.000Z
keywords: "astro, polar, parity pricing, ppp pricing, purchasing power parity, regional pricing, discount codes, vercel, api routes, typescript"
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Parity pricing con Polar y Astro" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/parity-pricing-polar-astro.png" />

El parity pricing (a menudo basado en paridad de poder adquisitivo / PPP) es una de las formas más sencillas de hacer tu producto asequible a nivel global sin bajar permanentemente tu precio de referencia. La implementación también es directa si ya tienes:

- Un proveedor de pagos que soporte **códigos de descuento** (Polar lo hace)
- Una forma de detectar el **país** del visitante en el servidor (las rutas API de Astro en Vercel lo facilitan)
- Un conjunto claro de **niveles de descuento** que estés dispuesto a ofrecer

Este tutorial recorre un sistema de parity listo para producción con **Polar + Astro**:

- Detectar el país **en el servidor**
- Mapear país → nivel (p. ej. 75 % / 65 % / 55 %…)
- Devolver una respuesta JSON de «oferta» que contenga **`percentOff`** y, opcionalmente, un **código de descuento de Polar**
- Renderizar un banner pequeño en el sitio de marketing y/o aplicar el descuento en el checkout

## Tabla de contenidos

- [Paso 1: Define tus niveles de parity (país → nivel de descuento)](#step-1-define-your-parity-tiers-country--discount-tier)
  - [Elegir una fuente de mapeo](#picking-a-mapping-source)
- [Paso 2: Detecta el país del visitante en el servidor](#step-2-detect-the-visitors-country-on-the-server)
- [Paso 3: Crea descuentos de parity en Polar (manual o vía API)](#step-3-create-parity-discounts-in-polar-manual-or-via-api)
  - [Estrategia: un código por nivel vs un código por país](#strategy-one-code-per-tier-vs-one-code-per-country)
  - [Automatizar la creación de descuentos (visión general)](#automating-discount-creation-high-level)
- [Paso 4: Devuelve «porcentaje de descuento» o «código de cupón» desde una ruta de servidor de Astro](#step-4-return-either-percent-off-or-coupon-code-from-an-astro-server-route)
- [Paso 5: Renderiza un banner de parity en Astro (lado del cliente)](#step-5-render-a-parity-banner-in-astro-client-side)
- [Salvaguardas para prevenir abusos](#guardrails-to-prevent-abuse)


## Requisitos previos

Necesitarás:

- [Node.js 20](https://nodejs.org/en/blog/announcements/v20-release-announce) o posterior
- Una cuenta de [Polar](https://polar.sh/) con al menos un producto
- Astro ejecutándose en **modo servidor** (`output: 'server'`) para que las rutas API se ejecuten en tiempo de ejecución
- (Recomendado) Despliegue en Vercel si quieres geolocalización de solicitudes integrada vía headers

## Visión general de la arquitectura

```
1. Navegador (sitio de marketing)
   → GET /api/parity-offer?product=astro (parámetro de producto opcional)
   → Recibe JSON: country, percentOff, eligible, código de cupón/descuento opcional

2. Ruta API de Astro (servidor)
   → Resuelve el país del visitante desde metadatos de la solicitud (p. ej. geolocalización de Vercel)
   → Busca el nivel PPP para ese país
   → Devuelve JSON de oferta para la UI + flujo de checkout

3. Polar (pagos)
   → Los códigos de descuento existen por nivel (o por producto, según tu estrategia)
   → El checkout aplica el código (prefijado o introducido por el usuario)
```

La decisión de diseño clave es: **el navegador nunca debe decidir el descuento**. La ruta del servidor debe calcular la oferta, porque el servidor tiene acceso a metadatos de solicitud confiables y puede aplicar salvaguardas.

## Paso 1: Define tus niveles de parity (país → nivel de descuento)

Empieza decidiendo los niveles que quieres ofrecer. Un enfoque pragmático es usar un puñado de niveles porcentuales (10 %, 15 %, 25 %, 35 %, 45 %, 55 %, 65 %, 75 %) y asignar países a cada nivel.

En tu propia aplicación Astro, añade un archivo `src/lib/parity.ts` donde cada nivel tenga:

- `pppRange`: puramente informativo (útil para docs/pantallas de admin)
- `percentOff`: el descuento que anunciar
- `couponCode`: un **único** código compartido por todos los países de ese nivel
- `countries`: los códigos ISO 3166-1 alpha-2 que pertenecen a ese nivel

Tus tipos principales pueden verse así:

```ts
export type ParityTierMeta = {
  pppRange: string
  percentOff: number
  couponCode: string
}

export type ParityOffer = {
  country: string
  countryName: string
  product: string
  percentOff: number
  eligible: boolean
  couponCode?: string
  pppRange?: string
}
```

Luego construye un mapa de búsqueda rápido una vez al inicializar el módulo:

```ts
function buildCountryParityMap(): Record<string, { percentOff: number; couponCode: string; pppRange: string }> {
  const map: Record<string, { percentOff: number; couponCode: string; pppRange: string }> = {}
  for (const tier of PARITY_TIERS) {
    for (const cc of tier.countries) {
      map[cc] = { percentOff: tier.percentOff, couponCode: tier.couponCode, pppRange: tier.pppRange }
    }
  }
  return map
}
```

Finalmente, expón un helper pequeño que devuelva una sola oferta:

```ts
export function getParityOffer({ country, product }: { country: string; product: string }): ParityOffer {
  const upper = country.toUpperCase()
  const rule = COUNTRY_PARITY[upper]
  const percentOff = rule?.percentOff ?? 0

  return {
    country: upper,
    countryName: new Intl.DisplayNames(['en'], { type: 'region' }).of(upper) ?? upper,
    product,
    percentOff,
    eligible: percentOff > 0,
    ...(rule?.couponCode ? { couponCode: rule.couponCode } : {}),
    ...(rule?.pppRange ? { pppRange: rule.pppRange } : {}),
  }
}
```

### Elegir una fuente de mapeo

Tienes opciones:

- **Niveles hardcodeados** (lo que hace este ejemplo): lo más simple, sin dependencias externas, fácil de razonar.
- **Niveles respaldados por base de datos**: útil si necesitas una UI de admin o quieres actualizar mapeos sin redesplegar.
- **Índices PPP dinámicos**: posible, pero rara vez vale la complejidad a menos que tu negocio dependa de ello.

Empieza con niveles hardcodeados; siempre puedes evolucionar más adelante.

## Paso 2: Detecta el país del visitante en el servidor

Necesitas un código de país para aplicar parity pricing. En Vercel, puedes derivarlo de los metadatos de geolocalización de la solicitud.

La ruta API de este repositorio usa `geolocation()` de `@vercel/functions` dentro de un `APIRoute` de Astro:

```ts title="src/pages/api/parity-offer.ts"
import { geolocation } from '@vercel/functions'
import type { APIRoute } from 'astro'
import { getParityOffer } from '../../lib/parity'

export const GET: APIRoute = async ({ request, url }) => {
  const product = url.searchParams.get('product') || 'default'
  const geo = geolocation(request)
  const country = geo.country || 'US'
  const offer = getParityOffer({ country, product })
  return new Response(JSON.stringify(offer), {
    headers: { 'content-type': 'application/json' },
  })
}
```

Si despliegas en otro lugar (o quieres más control), también puedes usar headers del proveedor (por ejemplo `x-vercel-ip-country`) o una búsqueda server-side de IP a país. Lo clave es que esto ocurre en el servidor y no puede falsificarse trivialmente desde el navegador.

## Paso 3: Crea descuentos de parity en Polar (manual o vía API)

Tus niveles de parity referencian códigos como `DRRBQY5P`. Esos códigos deben existir como descuentos/promociones en Polar para que el checkout los aplique.

Puedes crearlos de dos formas:

- **Dashboard (manual):** Crea un código de descuento por nivel (recomendado para empezar). Mantén el código corto, estable y reutilizable.
- **API de Polar (automatizado):** Crea (y actualiza) descuentos programáticamente vía https://polar.sh/docs/api-reference/discounts/create para que tu tabla de niveles sea la fuente de verdad.

### Estrategia: un código por nivel vs un código por país

- **Un código por nivel**: lo más fácil de gestionar, lo que usa la mayoría de equipos.
- **Un código por país**: te da control fino pero dispara la carga operativa.

En la mayoría de sistemas de parity, **por nivel** es el punto óptimo.

### Automatizar la creación de descuentos (visión general)

Polar expone APIs para gestionar entidades de comercio. Los nombres exactos de los endpoints pueden evolucionar, pero la arquitectura se mantiene igual:

1. Mantén una lista de definición de niveles en tu código (`PARITY_TIERS`)
2. Añade una ruta admin solo interna o un script puntual que:
   - Liste descuentos existentes de Polar
   - Cree descuentos faltantes para cada nivel
   - Asegure que el `percentOff` de cada nivel coincida con lo esperado
3. Almacena los identificadores/códigos de descuento resultantes de vuelta en tu mapa de niveles (o mantén códigos estables para no tener que escribir de vuelta)

Si quieres que el sitio devuelva un **código de descuento**, solo necesitas que el código exista en Polar.
Si quieres que el sitio devuelva un **porcentaje de descuento**, puedes mostrarlo en la UI aunque el código se aplique manualmente en el checkout.

## Paso 4: Devuelve «porcentaje de descuento» o «código de cupón» desde una ruta de servidor de Astro

La forma de tu respuesta API debe soportar ambos casos de uso:

- **UI de marketing**: mostrar «\(X\)% de descuento para clientes en \(Country\)» + opcionalmente «Usa el código ABCD»
- **Checkout**: aplicar el código automáticamente (si tu checkout soporta prefilling) o al menos mostrarlo de forma destacada

Este repositorio devuelve tanto `percentOff` como (cuando es elegible) `couponCode` desde `/api/parity-offer`.

Una mejora pequeña pero importante para producción es añadir caché:

- Las ofertas basadas en geolocalización no necesitan frescura a nivel de milisegundos.
- Cachea el JSON por un periodo corto en el edge (p. ej. 1 h–24 h) para reducir trabajo del servidor.

Por ejemplo:

```ts
return new Response(JSON.stringify(offer), {
  headers: {
    'content-type': 'application/json',
    // Cache en el CDN; siempre revalidar en el servidor cuando no esté en caché.
    'cache-control': 'max-age=0, s-maxage=86400',
  },
})
```

## Paso 5: Renderiza un banner de parity en Astro (lado del cliente)

Una vez tengas `/api/parity-offer`, puedes añadir un banner pequeño que muestre al usuario su descuento regional.

Un componente como `src/components/ParityBanner.astro` puede usar un script mínimo en el cliente:

- Obtiene `/api/parity-offer`
- Comprueba `eligible` y `percentOff`
- Construye una frase corta y opcionalmente añade el código si se proporciona

```astro title="src/components/ParityBanner.astro"
<div id="parity-banner" class="hidden w-full border-b py-2.5 text-center" role="status" aria-live="polite">
  <div class="mx-auto flex max-w-7xl flex-wrap items-center justify-center gap-x-2 gap-y-1 px-4 text-sm leading-snug">
    <span id="parity-flag" class="shrink-0 text-lg leading-none" aria-hidden="true"></span>
    <p id="parity-text" class="font-sans"></p>
  </div>
</div>

<script>
  function countryCodeToFlagEmoji(code) {
    const upper = String(code).toUpperCase()
    if (upper.length !== 2 || !/^[A-Z]{2}$/.test(upper)) return ''
    return String.fromCodePoint(...[...upper].map((c) => 127397 + c.charCodeAt(0)))
  }

  const banner = document.getElementById('parity-banner')
  const flagEl = document.getElementById('parity-flag')
  const textEl = document.getElementById('parity-text')
  if (!banner || !flagEl || !textEl) {
    /* skip */
  } else {
    fetch('/api/parity-offer')
      .then((r) => r.json())
      .then((data) => {
        if (!data || !data.eligible || !data.percentOff || data.percentOff <= 0) return
        flagEl.textContent = countryCodeToFlagEmoji(data.country ?? '')

        const pct = data.percentOff
        const place = data.countryName || data.country || ''
        const parts = []
        parts.push(`<strong class="font-bold">${pct}%</strong>`)
        parts.push(` discount for customers in `)
        parts.push(`<strong class="font-bold">${place}</strong>!`)
        if (data.couponCode) {
          parts.push(` Use code `)
          parts.push(`<strong class="font-bold">${data.couponCode}</strong>`)
        }
        textEl.innerHTML = parts.join('')
        banner.classList.remove('hidden')
      })
      .catch(() => {})
  }
</script>
```

## Salvaguardas para prevenir abusos

El parity pricing es un sistema de descuentos, así que necesitas salvaguardas básicas:

- **Decisión solo en servidor:** nunca calcules el nivel en el navegador.
- **Limitar y validar:** solo devuelve códigos que existan en tu tabla de niveles.
- **Alcance por producto:** si algunos productos no deben tener descuentos de parity, devuelve `eligible: false` para esos.

Si necesitas una aplicación más estricta (por ejemplo, evitar que los usuarios compartan un código de nivel alto), considera emitir mecanismos de descuento **de corta duración y específicos por usuario** en lugar de códigos compartidos. Es más complejo, pero es el siguiente paso cuando el parity se convierte en una palanca de ingresos material.

## Conclusión

El parity pricing no requiere un gran esfuerzo de implementación cuando estás empezando con tu SaaS. Con Astro, Vercel y Polar, puedes implementarlo como:

- Un mapa de niveles determinista (país → porcentaje + código)
- Una única ruta API que resuelve el país en el servidor y devuelve un payload JSON de oferta
- Una superficie de UI mínima que comunica el descuento con claridad

A partir de ahí, puedes automatizar la creación de descuentos vía la API de Polar, añadir reglas de elegibilidad por producto y reforzar la aplicación a medida que crece tu volumen.
