Caso de estudio 1: Una página de aterrizaje multilingüe con pagos
La idea
Una amiga vende una guía en PDF por $29 para personas extranjeras que se mudan a Corea. Quería una sola página que cargara rápido en todo el mundo, hablara inglés, coreano y chino, y aceptara pagos con tarjeta sin que ella tuviera que tocar un servidor.
La especificación
Mantuvimos la especificación en un solo párrafo antes de escribir código:
Build a single-page marketing site for a $29 digital product.
Three languages (en/ko/zh), auto-detected from the browser but
switchable with a toggle. One "Buy" button that opens Stripe
Checkout and redirects to a thank-you page on success. No backend
server I have to maintain. Must load fast from anywhere.
Fíjate en lo que la especificación no dice: ningún , ningún sistema de diseño, ninguna analítica, ninguna captura de correo electrónico. Cada una de esas habría sido una función razonable, y cada una habría retrasado el primer lanzamiento. La disciplina de una especificación de un párrafo es, en gran medida, la disciplina de dejar cosas fuera.
La pila tecnológica
"Sin que yo mantenga" más "rápido en todo el mundo" apuntaba directo a alojamiento estático en una CDN de borde (Content Delivery Network — red de distribución de contenido: copias de tus archivos guardadas en centros de datos de todo el mundo para que cada visitante cargue desde uno cercano) con una pequeña función sin servidor para lo único que necesita un : crear la de Stripe. Elegimos un sitio estático en Cloudflare Pages con una sola Pages Function. Toda la arquitectura cabe en una frase: una página estática que llama a una función que habla con Stripe, y esa simplicidad fue lo que la hizo depurable después.
Todo el sistema son tres cajas, y solo la del medio toca un secreto:
┌─────────────┐ POST /api/checkout ┌──────────────┐ secret key ┌────────┐
│ STATIC PAGE │─────────────────────▶│ PAGES │─────────────▶│ STRIPE │
│ (HTML/JS) │◀─────────────────────┤ FUNCTION │◀─────────────┤ │
│ en/ko/zh │ checkout URL │ (the only │ session URL └────────┘
└─────────────┘ │ secret-side)│
served from CDN edge └──────────────┘
Los prompts clave
Empezamos en general y dejamos que la IA propusiera la estructura:
Scaffold a static landing page (plain HTML/CSS/JS, no framework)
with a language toggle for en/ko/zh. Store the copy in a single
JS object keyed by language. Detect the default from
navigator.language. Keep it to one index.html plus one main.js.
Luego el camino de pago, donde vive el secreto:
Add a Cloudflare Pages Function at /api/checkout that creates a
Stripe Checkout session for a single $29 product and returns the
redirect URL. Read STRIPE_SECRET_KEY from the environment, never
hardcode it. The Buy button should POST to this function and then
window.location to the returned URL.
La frase "never hardcode it" (nunca lo codifiques directamente) se gana su lugar. Si se la deja a sus propios valores por defecto, una IA felizmente coloca una clave de marcador de posición en línea para que el ejemplo "funcione", y un marcador de posición tiene la costumbre de convertirse en una clave real que termina en producción. Decirlo una vez en voz alta, en el , es más barato que atraparlo en la revisión.
El obstáculo
La primera prueba en vivo falló: el botón de comprar no hacía nada, y la consola del navegador mostraba un error de CORS (Cross-Origin Resource Sharing — el intercambio de recursos de origen cruzado: la regla del navegador sobre qué sitios pueden llamar a qué otros sitios). No adivinamos. Pegamos el error exacto de vuelta:
Clicking Buy logs: "Access to fetch at '/api/checkout' blocked
by CORS policy." The function and page are on the same domain.
Here's the function code: [pasted]. What's actually wrong?
La IA lo detectó al instante: la función estaba devolviendo la de Stripe como (JavaScript Object Notation — un formato de texto simple y legible que usan los programas para intercambiar datos estructurados), pero el fetch se estaba haciendo antes de que se manejara el preflight OPTIONS de la función, porque habíamos desplegado por error la página estática y la función en dos proyectos de Pages distintos. La solución real era de topología de despliegue, no de código. Movimos la función al directorio /functions del mismo proyecto y el error desapareció.
Vale la pena detenerse en la lección: el síntoma estaba en el código (un error de CORS proveniente de un fetch), pero la causa estaba en la infraestructura (dos proyectos en lugar de uno). Si hubiéramos dejado que la IA "arreglara el error de CORS" sin pegar la configuración, habría añadido cabeceras Access-Control-Allow-Origin como parche: una solución que compila, disimula el síntoma y deja viva la causa real del error. Pega el error literal y el contexto que lo rodea, y no aceptes la primera explicación plausible si no coincide con tu configuración.
El lanzamiento
Añadimos las claves de Stripe reales como variables de entorno cifradas en el panel de Pages (nunca en el repositorio), apuntamos el (Domain Name System, el directorio de direcciones de internet que mapea un nombre a un servidor) de su hacia Pages, e hicimos una compra de prueba real de $29 con una tarjeta, que luego reembolsamos. También recorrimos a propósito el camino de fallo, con una tarjeta de prueba rechazada, para confirmar que no se le cobraba y que veía un mensaje coherente en vez de una pantalla en blanco. En marcha en una tarde. Hizo su primera venta esa misma noche.