La Pesadilla de Depurar HTTP/HTTPS: Un Viaje de 24 Horas por el Infierno del Mixed Content
Pasé 24 horas depurando por qué mi app de React seguía haciendo peticiones HTTP desde páginas HTTPS, aunque mi código las convertía. El culpable me dejó de piedra.
---
Actualización (noviembre de 2025): ¡STRAŦUM ya está en Private Alpha! La plataforma de marketing de 9 agentes mencionada en este post está aceptando testers anticipados. Solicita acceso en stratum.chandlernguyen.com o lee la historia completa del lanzamiento.
---
Contexto: El Viaje con la Plataforma de Marketing Continúa
¿Recuerdas ese post de septiembre donde dije que estaba corriendo a toda velocidad para construir una plataforma de marketing de 10 agentes mientras echaba siestecitas? Cuatro semanas después, tenía 3 agentes funcionando y apuntaba a octubre/noviembre para el lanzamiento alfa.
Bueno, ya es finales de octubre. Hora de una actualización de progreso.
Las Buenas Noticias:
- La plataforma por fin tiene nombre: STRAŦUM (Intelligence Over Execution)
- Guías de marca completas y sistema de diseño (resulta que el branding lleva más tiempo que el código)
- 9 de 10 agentes construidos e integrados
- Arquitectura multi-tenant funcionando de verdad
- Fase final: preparándose para la fase pre-alfa, pruebas solo por invitación
La Dosis de Realidad:
Estuve enfermo 10 días después de estar de baja 8 días (ya sé, estas cosas pasan). Eso detuvo todo y desplazó mis plazos. Así que adiós al lanzamiento de octubre, ¿no?
Pero aquí está la cosa del desarrollo en solitario: tú controlas el ritmo. Sin presión para lanzar cuando no estás listo. Sin inversores encima de tu cuello. Solo... hacerlo bien.
Este post es sobre uno de esos momentos de "hacerlo bien" que se convirtió en una maratón de depuración de 6 horas. Porque justo cuando volvía a toda velocidad después de haber estado enfermo, mi plataforma de despliegue decidió tener otros planes.
Cuando AWS Cae, los Ingenieros se Ponen Creativos (y a Veces se Arrepienten)
Hay algo que no te enseñan en los bootcamps de programación: a veces tu plataforma de despliegue simplemente... desaparece. No por algo que hayas hecho mal, sino porque AWS decidió tener una interrupción masiva que afectó a los despliegues de Vercel a nivel global.
Así empezó mi lunes. Mi sitio en producción estaba caído, Vercel mostraba errores y yo tenía exactamente un pensamiento: "Necesito un backup. Rápido."
Entra en escena Cloudflare Pages. Había escuchado cosas buenas. Gran CDN, despliegues automáticos, configuración sencilla. ¿Qué podía salir mal?
Narrador: Todo. Todo podía salir mal.
El Cambio Que Parecía Demasiado Fácil
La migración a Cloudflare Pages fue sorprendentemente fluida. Conecté mi repositorio de GitHub, configuré las variables de entorno en el panel, hice push a main. Tres minutos después: desplegado.
"Vaya," pensé. "Esto casi parece demasiado fácil."
Luego abrí el sitio en producción.
```
Mixed Content: The page at 'https://my-site.com/...' was loaded over HTTPS,
but requested an insecure resource 'http://stratum-api.us-central1.run.app/...'
```
¿Esa sensación de hundimiento cuando te das cuenta de que tu celebración fue prematura? Sí, esa.
El Problema: Llamadas HTTP Desde una Página HTTPS
Mi app de React estaba haciendo peticiones HTTP a mi API de backend mientras la propia página se cargaba por HTTPS. Los navegadores (con razón) bloquean esto como un riesgo de seguridad. Errores de Mixed Content. Todas y cada una de las llamadas a la API fallaban.
"Pero espera," me dije, "¡tengo `ensureHttpsInProduction()` en mi código! ¡Se supone que convierte HTTP a HTTPS automáticamente!"
Revisé el bundle desplegado. La función estaba ahí. La lógica era correcta. La consola del navegador mostraba que la conversión ocurría. Entonces, ¿por qué seguían pasando peticiones HTTP?
Primer Intento de Depuración: La Caza de Variables de Entorno
¿Quizás las variables de entorno en Cloudflare no se estaban recogiendo?
```bash
# Revisé el Panel de Cloudflare
VITE_API_URL=https://stratum-api.us-central1.run.app ✓
VITE_SUPABASE_URL=https://your-project.supabase.co ✓
```
Todo en HTTPS. Todo correcto.
Lancé una reconstrucción. Esperé. Desplegado. Abrí el sitio.
El mismo error. Seguían las peticiones HTTP.
Segundo Intento: La Gran Reimportación
¿Quizás los archivos no usaban el `API_BASE_URL` centralizado?
Pasé la siguiente hora actualizando 24 archivos para importar desde `@/lib/api` en lugar de usar directamente `import.meta.env.VITE_API_URL`. Cada componente que hacía llamadas a la API recibió el tratamiento.
```typescript
// Antes
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/v1/...`);
// Después
import \{ API_BASE_URL \} from '@/lib/api';
const response = await fetch(`$\{API_BASE_URL\}/api/v1/...`);
```
Push. Despliegue. Esperé.
Seguía roto.
En este punto, empiezo a cuestionar mis decisiones vitales.
El Giro Inesperado: Archivos que Faltaban en Git
Pero espera, la cosa se pone peor.
Mientras investigaba por qué mi aplicación de HTTPS no funcionaba, descubrí algo aterrador. El archivo `api.ts` que contenía `ensureHttpsInProduction()` ni siquiera estaba en mi repositorio de Git.
Tampoco estaba `authService.ts`. Ni `csvSanitizer.ts`. Tres archivos críticos de frontend simplemente... desaparecidos.
¿Cómo? El archivo `.gitignore` tenía esto:
```
# Python stuff
lib/
build/
dist/
```
Parece razonable para Python, ¿verdad? Excepto que mis utilidades de frontend vivían en `apps/web/src/lib/`. ¡El patrón amplio `lib/` estaba ignorando accidentalmente todo mi directorio lib de frontend!
Esto significaba:
1. Cloudflare estaba construyendo desde el repositorio (sin estos archivos)
2. Mi entorno de desarrollo local tenía estos archivos (funcionando bien localmente)
3. Yo NO TENÍA IDEA de que no estaban siendo rastreados
La corrección:
```diff
# .gitignore - Antes
-lib/
# .gitignore - Después
+apps/api/lib/ # Python-specific
+!apps/web/src/lib/ # Explicitly include frontend lib
```
Añadí los archivos que faltaban, hice commit, push. ¡Ahora Cloudflare tenía el código de aplicación de HTTPS!
Excepto que... los errores HTTP continuaron.
Tercer Intento: Validación en Tiempo de Compilación
En este punto, lo cuestiono todo. "¿Sabes qué?" pensé. "Si esto sigue pasando, necesito IMPEDIRLO de llegar alguna vez a producción."
Escribí un plugin de Vite que falla la compilación si detecta URLs HTTP en producción:
```typescript
function validateProductionUrls(mode: string) \{
if (mode !== 'production') return;
const apiUrl = process.env.VITE_API_URL || '';
if (apiUrl && apiUrl.trim().startsWith('http://')) {
if (!apiUrl.includes('localhost') && !apiUrl.includes('127.0.0.1')) {
throw new Error(
`❌ HTTPS ENFORCEMENT FAILED
Environment Variable: VITE_API_URL
Current Value: ${apiUrl\}
This will cause Mixed Content errors in production!`
);
}
}
}
```
Genial, ¿verdad? Ahora es IMPOSIBLE desplegar con URLs HTTP.
Desplegué de nuevo. La compilación pasó (las variables de entorno eran HTTPS). El sitio cargó.
El mismo. Maldito. Error.
El Momento de Iluminación: Se Estaban Desplegando Archivos Locales
Tarde (muy tarde) por la noche, tuve una revelación.
Revisé de nuevo el bundle de JavaScript desplegado. Esta vez lo miré de verdad. La URL dentro era:
```javascript
"http://stratum-api.us-central1.run.app"
```
Pero mis variables de entorno de Cloudflare eran HTTPS. Entonces, ¿de dónde venía esta URL HTTP?
Entonces me cayó el veinte. Mi archivo local `.env.production`.
```bash
# apps/web/.env.production (ARCHIVO LOCAL)
VITE_API_URL=http://stratum-api.us-central1.run.app
```
¡Cloudflare Pages estaba desplegando mi archivo de entorno local en lugar de usar las variables del panel!
Revisé `.cloudflare-pages-ignore`:
```
# Environment files
.env
.env.local
.env.development
.env.test
# .env.production ← ¡FALTABA!
```
Me golpeé la frente con la palma de la mano.
La Corrección: Una Sola Línea
```diff
# apps/web/.cloudflare-pages-ignore
.env
.env.local
.env.development
.env.test
+.env.production
```
Desplegado. Esperé.
¡Esta vez un error diferente! ¡Progreso!
```
Access to fetch at 'https://stratum-api.us-central1.run.app/...'
from origin 'https://preview-xyz.stratum-marketing-suite.pages.dev'
has been blocked by CORS policy
```
¡Errores CORS! ¡Preciosos, preciosos errores CORS! ¡Eso significaba que HTTPS estaba funcionando!
Pero Espera, Hay Más: La Conspiración del Caché
Corregí CORS. Desplegué de nuevo. Abrí mi dominio personalizado.
Errores HTTP de nuevo.
¿¡Qué!?
Resulta que el CDN de Cloudflare estaba guardando en caché el bundle antiguo de forma agresiva. El nuevo despliegue (con HTTPS) estaba activo en la URL de preview, pero mi dominio personalizado servía contenido en caché con URLs HTTP.
La purga de caché de Cloudflare requiere:
1. Encontrar la configuración de zona correcta (no está en el panel de Pages)
2. Navegar por la configuración del dominio (no es obvio)
3. Purgar la caché manualmente (en cada despliegue)
Después de horas depurando problemas HTTP/HTTPS, tomé una decisión.
El Regreso a Vercel: A Veces Lo Aburrido es Mejor
AWS había vuelto. Vercel funcionaba de nuevo.
Migré todo de vuelta a Vercel. ¿Por qué?
1. Invalidación automática de caché: No hace falta purgar manualmente
2. Manejo más sencillo de variables de entorno: Lo que configuras es lo que obtienes
3. Depuración más rápida: Menos infraestructura que razonar
4. Battle-tested: Conozco sus peculiaridades
El despliegue en Vercel tardó 3 minutos. Sin errores HTTP. Sin problemas de caché. Simplemente... funcionó.
Lo Que Aprendí (A Las Malas)
1. Ignora Siempre .env.production en las Plataformas de Despliegue
```
# .vercelignore
# .cloudflare-pages-ignore
# .netlify-ignore
.env
.env.local
.env.development
.env.test
.env.production ← NO OLVIDES ESTO
```
2. Los Patrones Amplios en .gitignore Son Peligrosos en Monorepos
```diff
# ❌ Malo - Ignora carpetas lib tanto del frontend como del backend
-lib/
-build/
-dist/
# ✅ Bueno - Específico para cada contexto
+apps/api/lib/ # Python-specific
+apps/api/build/
+apps/api/dist/
+apps/web/dist/ # Vite output only
```
Pregúntate siempre: "¿Podría este patrón ignorar accidentalmente algo importante?"
En un monorepo con varios lenguajes (Python + TypeScript), los patrones amplios pensados para un ecosistema pueden ignorar accidentalmente archivos críticos de otro.
3. La Validación en Tiempo de Compilación Sigue Valiendo la Pena
Aunque no detectó el problema del archivo local, la validación en tiempo de compilación previene *futuras* configuraciones incorrectas:
```typescript
// vite.config.ts
export default defineConfig((\{ mode \}) => \{
validateProductionUrls(mode);
return {
// ... config
\};
});
```
4. La Defensa en Múltiples Capas Funciona
Nuestra arquitectura final tiene TRES capas:
- En tiempo de compilación: Falla la compilación si se detectan URLs HTTP
- En tiempo de ejecución: Convierte HTTP → HTTPS si la página se cargó por HTTPS
- En el despliegue: Excluye los archivos .env locales
5. Las URLs de Preview Son Tus Amigas
Prueba siempre primero en la URL de preview. Si eso funciona pero tu dominio personalizado no, suele ser caché.
6. Conoce las Peculiaridades de Tu Plataforma
- Vercel: Simple, invalida la caché automáticamente, las variables de entorno "simplemente funcionan"
- Cloudflare Pages: Gran CDN, pero purga de caché manual y configuración más compleja
El Código Que Me Salvó
Aquí está la función final `ensureHttpsInProduction()`:
```typescript
function ensureHttpsInProduction(url: string): string \{
// Only convert in browser context when site is loaded over HTTPS
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
// Don't convert localhost/127.0.0.1 URLs (local development)
if (url.startsWith('http://') &&
!url.includes('localhost') &&
!url.includes('127.0.0.1')) {
console.warn('[API] Converting HTTP to HTTPS:', url);
return url.replace('http://', 'https://');
\}
}
return url;
}
```
Y la validación en tiempo de compilación en `vite.config.ts`:
```typescript
function validateProductionUrls(mode: string) \{
if (mode !== 'production') return;
const apiUrl = process.env.VITE_API_URL || '';
// Check for HTTP (should be HTTPS)
if (apiUrl && apiUrl.trim().startsWith('http://')) {
if (!apiUrl.includes('localhost') && !apiUrl.includes('127.0.0.1')) {
throw new Error(`
❌ HTTPS ENFORCEMENT FAILED
Environment Variable: VITE_API_URL
Current Value: ${apiUrl\}
Mixed Content Error Prevention:
Browsers block HTTP requests from HTTPS pages.
Fix: Update environment variables to use HTTPS URLs.
`);
}
}
}
```
La Lección Real: Depurar es Trabajo de Detective
Esto no era un problema de código. Era una expedición arqueológica de configuración.
Los bugs reales eran:
1. ✅ Patrones amplios en `.gitignore` que ignoraban archivos críticos de frontend
2. ✅ `.env.production` ausente en `.cloudflare-pages-ignore`
3. ✅ Caché agresivo del CDN que enmascaraba la corrección
4. ✅ Asumir la precedencia de las variables de entorno
La solución técnica fue una sola línea en un archivo `.ignore`.
¿La depuración? Eso tomó 6 horas, 14 despliegues y demasiado café.
¿Valió la Pena?
Absolutamente. Esto es lo que gané:
1. Comprensión profunda de las políticas de seguridad de Mixed Content
2. Validación en tiempo de compilación que previene problemas futuros
3. Aplicación de HTTPS en múltiples capas que es agnóstica a la plataforma
4. Apreciación real por la simplicidad de Vercel
Y lo más importante: una gran historia de depuración para compartir. :P
El Log de Git Cuenta la Historia
```bash
2033b9a fix: add .env.production to .vercelignore
3555390 docs: migrate deployment documentation from Cloudflare Pages to Vercel
a4edb09 chore: force clean Cloudflare Pages rebuild
590c271 fix: enhance ensureHttpsInProduction logging
15d69c6 chore: force rebuild of UserProfile bundle
a1c345f fix: add .env.production to cloudflare-pages-ignore ← THE .ENV FIX
635d80d docs: update documentation for Cloudflare Pages migration
edfa611 feat: add build-time HTTPS enforcement
802c1e9 fix: enforce HTTPS for all API calls across frontend
26f6b87 refactor: comprehensive .gitignore audit and cleanup
3758a9c fix: unignore frontend lib directory and add missing files ← THE GITIGNORE FIX
```
Cada commit representa una hora de depuración en el mundo antiguo, pero con Claude Code, gracias a dios, mi velocidad fue mucho mayor. Una hipótesis probada. Una lección aprendida.
Para Otros Ingenieros que Luchan con Errores de Mixed Content
Si estás leyendo esto porque estás depurando el mismo problema, aquí tienes tu checklist:
1. Comprueba Tus Variables de Entorno:
```bash
# Imprime los valores reales que se están usando
console.log('API URL:', import.meta.env.VITE_API_URL);
```
2. Comprueba Tu Bundle Desplegado:
```bash
# Descarga y busca en el bundle de JavaScript
curl https://your-site.com/assets/index-ABC123.js | grep "http://"
```
3. Comprueba Tus Archivos Ignore:
```bash
# Asegúrate de que .env.production esté excluido
cat .vercelignore
cat .cloudflare-pages-ignore
cat .netlify-ignore
```
4. Comprueba Tu .gitignore (Monorepos):
```bash
# Asegúrate de que los archivos críticos no estén siendo ignorados
git ls-files apps/web/src/lib/ # Debería mostrar api.ts, etc.
# Si está vacío, busca patrones amplios
grep "^lib/" .gitignore # ❌ Demasiado amplio
grep "^apps/api/lib/" .gitignore # ✅ Específico
```
5. Comprueba Tu Caché:
```bash
# Prueba primero en la URL de preview
# Si la preview funciona pero la producción no = problema de caché
```
6. Añade Validación en Tiempo de Compilación:
```typescript
// Evita que vuelva a ocurrir
if (mode === 'production' && url.startsWith('http://')) \{
throw new Error('HTTPS required in production!');
\}
```
El Checklist de Migración que Ojalá Hubiera Tenido
Al cambiar de plataforma de despliegue:
- [ ] Listar TODAS las variables de entorno de la plataforma antigua
- [ ] Configurar las variables de entorno en la nueva plataforma PRIMERO
- [ ] Añadir TODOS los archivos .env al .ignore (incluyendo .env.production)
- [ ] Verificar que .gitignore no está ignorando archivos críticos (ejecuta `git ls-files` para comprobarlo)
- [ ] Probar en la URL de preview antes del dominio personalizado
- [ ] Comprobar el bundle desplegado en busca de URLs HTTP
- [ ] Verificar la configuración de CORS si el backend es independiente
- [ ] Documentar las peculiaridades específicas de la plataforma
Seguimos Programando, Seguimos Aprendiendo, Seguimos Rompiendo Cosas (A Veces)
Seis horas de depuración para una corrección de una línea. Eso es la ingeniería de software en pocas palabras.
El código que enviamos es importante, claro. ¿Pero las habilidades de depuración que desarrollamos? Esas son las que nos hacen mejores ingenieros.
La próxima vez que mi plataforma de despliegue caiga (y habrá una próxima vez), estaré listo. Tengo:
- ✅ Aplicación de HTTPS agnóstica a la plataforma
- ✅ Validación en tiempo de compilación
- ✅ Mejor comprensión de la precedencia de variables de entorno
- ✅ Una estrategia de despliegue de respaldo
Y con suerte, este post le ahorrará a alguien unas cuantas de esas 6 horas. :)
¿Alguna vez has tenido una "corrección de una línea" que te tomó un tiempo vergonzosamente largo encontrar? Me encantaría escuchar tus historias de detective de depuración: ¡el sufrimiento comparte compañía!
Un abrazo,
Chandler





