Skip to content

Checklist de producción ​

Llegó el día: el sitio del cliente está terminado y hay que lanzarlo. Esta lección es el proyecto final del curso: un recorrido completo, paso a paso, para publicar el sitio de un cliente sin olvidar nada. Imprímela, cópiala a tu gestor de tareas y úsala en cada proyecto.

El escenario ​

"Clínica Dental Sonrisa" te contrató un sitio con:

  • Páginas informativas + blog (frontend estático con VitePress o Astro).
  • Formulario de citas que guarda en base de datos y envía correo (API en Node o PHP).
  • Dominio clinicasonrisa.com y correo citas@clinicasonrisa.com.

Arquitectura elegida:

txt
clinicasonrisa.com      → VPS Ubuntu 24.04: Nginx (estáticos) + API Node con PM2 + PostgreSQL
www                     → redirige al dominio raíz
correo                  → Google Workspace (o Zoho Mail)
DNS + CDN               → Cloudflare
Respaldos               → script diario + rclone a almacenamiento externo
Monitoreo               → UptimeRobot + Sentry
Despliegue              → GitHub Actions al hacer push a main

Fase 1: semanas antes del lanzamiento ​

Cuentas y accesos ​

  • [ ] Dominio registrado a nombre del cliente (tú como colaborador o con acceso delegado).
  • [ ] Renovación automática del dominio activada y método de pago del cliente.
  • [ ] Cuenta del VPS / hosting a nombre del cliente o facturada claramente en tu plan de mantenimiento.
  • [ ] Todas las contraseñas en un gestor (Bitwarden, 1Password) compartido con el cliente.
  • [ ] Autenticación de dos factores en registrador, hosting, Cloudflare, GitHub y correo.

Servidor ​

  • [ ] VPS configurado según Configurar un VPS desde cero: usuario sudo, SSH con llaves, ufw, fail2ban, actualizaciones automáticas.
  • [ ] Nginx con server blocks para el sitio y la API (Nginx).
  • [ ] App gestionada con PM2 y arranque automático (Node con PM2).
  • [ ] Base de datos con usuario propio, sin puerto público.
  • [ ] Entorno de staging (staging.clinicasonrisa.com) donde el cliente aprobó todo.

Fase 2: configuración de producción ​

Variables de entorno y secretos ​

  • [ ] Archivo .env de producción en el servidor, con permisos 600/640, fuera de Git.
  • [ ] Modo depuración desactivado: NODE_ENV=production, APP_DEBUG=false, display_errors=Off.
  • [ ] Claves y tokens de producción distintos a los de desarrollo.
  • [ ] Llaves de API restringidas por dominio o IP cuando el servicio lo permita (Google Maps, reCAPTCHA).
env
# .env de producción (ejemplo)
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://clinica_app:CLAVE_LARGA@localhost:5432/clinica
SMTP_HOST=smtp.proveedor.com
SMTP_USER=citas@clinicasonrisa.com
SMTP_PASS=CLAVE_DE_APLICACION
CORS_ORIGIN=https://clinicasonrisa.com
SENTRY_DSN=https://xxxx@sentry.io/123

Seguridad ​

  • [ ] HTTPS con certificado válido y redirección HTTP → HTTPS (SSL).
  • [ ] Cabeceras de seguridad: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, HSTS.
  • [ ] Archivos ocultos bloqueados (.git, .env): prueba https://clinicasonrisa.com/.env → debe dar 403/404.
  • [ ] Formularios con validación en servidor, protección anti-spam (honeypot, reCAPTCHA o Turnstile) y límite de peticiones.
  • [ ] Consultas a la base con parámetros preparados (sin concatenar SQL).
  • [ ] CORS restringido al dominio del sitio.
  • [ ] Dependencias sin vulnerabilidades críticas: npm audit / composer audit.

Server block final de referencia:

nginx
# /etc/nginx/sites-available/clinicasonrisa.com
server {
    listen 80;
    listen [::]:80;
    server_name clinicasonrisa.com www.clinicasonrisa.com;
    return 301 https://clinicasonrisa.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.clinicasonrisa.com;
    ssl_certificate     /etc/letsencrypt/live/clinicasonrisa.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/clinicasonrisa.com/privkey.pem;
    return 301 https://clinicasonrisa.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name clinicasonrisa.com;

    ssl_certificate     /etc/letsencrypt/live/clinicasonrisa.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/clinicasonrisa.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;

    root /var/www/clinicasonrisa.com/dist;
    index index.html;

    # Cabeceras de seguridad
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    access_log /var/log/nginx/clinica.access.log;
    error_log  /var/log/nginx/clinica.error.log;

    # API
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Sitio estático con URLs limpias
    location / {
        try_files $uri $uri.html $uri/ =404;
    }

    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Strict-Transport-Security "max-age=31536000" always;
        add_header X-Content-Type-Options "nosniff" always;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }

    error_page 404 /404.html;
}

Y el límite de peticiones, en http { } de /etc/nginx/nginx.conf:

nginx
# 10 peticiones por segundo por IP para la zona "api"
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

Correo ​

  • [ ] Registros MX del proveedor de correo.
  • [ ] SPF, DKIM y DMARC configurados (Dominios y DNS).
  • [ ] Los correos del formulario se envían por SMTP autenticado o un servicio transaccional (no con mail() sin autenticar).
  • [ ] Prueba: envía un correo a mail-tester.com y apunta a 9/10 o más.

Fase 3: contenido, SEO y experiencia ​

  • [ ] Cada página con title único y meta description.
  • [ ] Etiquetas Open Graph (imagen, título y descripción al compartir en WhatsApp y redes).
  • [ ] favicon e íconos para móvil.
  • [ ] sitemap.xml generado y robots.txt correcto.
  • [ ] Página 404 personalizada y útil.
  • [ ] Textos revisados (ortografía, teléfonos, direcciones, horarios).
  • [ ] Aviso de privacidad / política de cookies según la legislación del país del cliente.
  • [ ] Imágenes optimizadas y con alt descriptivo.
  • [ ] Accesibilidad básica: contraste, navegación con teclado, etiquetas en formularios.
  • [ ] Probado en Chrome, Firefox, Safari y en celulares reales (Android e iPhone).
  • [ ] Lighthouse ≥ 90 en rendimiento, accesibilidad, buenas prácticas y SEO (Rendimiento).
txt
# robots.txt de producción
User-agent: *
Allow: /
Disallow: /api/

Sitemap: https://clinicasonrisa.com/sitemap.xml

El robots.txt de staging

Es muy común lanzar con el robots.txt de staging que contiene Disallow: /, y el sitio no aparece en Google. Revísalo el día del lanzamiento. En staging, mejor protege con contraseña (auth básica).

Fase 4: operación ​

Despliegue ​

  • [ ] Workflow de GitHub Actions que despliega al hacer push a main (CI/CD).
  • [ ] Rama main protegida; cambios mediante pull request.
  • [ ] Procedimiento de rollback documentado (volver al commit anterior y redesplegar).

Respaldos y monitoreo ​

  • [ ] Respaldo diario de base de datos y archivos subidos, copiado fuera del servidor (Monitoreo y respaldos).
  • [ ] Restauración probada al menos una vez.
  • [ ] Snapshots automáticos del proveedor activados.
  • [ ] Monitor de disponibilidad (UptimeRobot / Better Stack) con alertas a tu correo o Telegram.
  • [ ] Sentry (o similar) capturando errores de la app.
  • [ ] Alerta de expiración de certificado y de dominio.

Analítica ​

  • [ ] Google Search Console verificado (registro TXT) y sitemap enviado.
  • [ ] Analítica instalada (Google Analytics 4, Plausible, Umami o Cloudflare Web Analytics), respetando el aviso de privacidad.
  • [ ] Ficha de Google Business Profile apuntando al sitio (clave para negocios locales).

Fase 5: el día del lanzamiento ​

Orden recomendado:

  1. 24 h antes: baja el TTL de los registros DNS a 300 segundos.
  2. Haz un respaldo completo del sitio anterior (si existía).
  3. Despliega la versión final en el servidor de producción.
  4. Prueba con el archivo hosts o con curl -H "Host: clinicasonrisa.com" https://IP antes de cambiar el DNS.
  5. Cambia los registros DNS (A / CNAME) al servidor nuevo.
  6. Obtén o verifica el certificado SSL.
  7. Recorre el sitio completo en producción: cada página, cada enlace, cada formulario.
  8. Envía una cita de prueba y confirma que llega el correo y se guarda en la base.
  9. Revisa robots.txt, sitemap.xml y que no haya errores en la consola del navegador.
  10. Envía el sitemap a Search Console.
  11. Avisa al cliente con un mensaje claro y celebra.

Script rápido de verificación post-lanzamiento:

bash
#!/usr/bin/env bash
# verificar.sh — comprobaciones básicas tras el lanzamiento
DOMINIO="clinicasonrisa.com"

echo "== DNS =="
dig +short "$DOMINIO" A
dig +short "www.$DOMINIO"

echo "== Redirecciones =="
curl -s -o /dev/null -w "http://       → %{http_code} %{redirect_url}\n" "http://$DOMINIO"
curl -s -o /dev/null -w "https://www   → %{http_code} %{redirect_url}\n" "https://www.$DOMINIO"
curl -s -o /dev/null -w "https://      → %{http_code}\n" "https://$DOMINIO"

echo "== Archivos que NO deben ser públicos =="
for ruta in .env .git/config; do
  curl -s -o /dev/null -w "/$ruta → %{http_code}\n" "https://$DOMINIO/$ruta"
done

echo "== SEO =="
curl -s -o /dev/null -w "robots.txt  → %{http_code}\n" "https://$DOMINIO/robots.txt"
curl -s -o /dev/null -w "sitemap.xml → %{http_code}\n" "https://$DOMINIO/sitemap.xml"

echo "== API =="
curl -s "https://$DOMINIO/api/salud"; echo

echo "== Certificado =="
echo | openssl s_client -connect "$DOMINIO:443" -servername "$DOMINIO" 2>/dev/null \
  | openssl x509 -noout -dates

Fase 6: la semana siguiente ​

  • [ ] Revisa los logs de error diariamente los primeros días.
  • [ ] Revisa Search Console: errores de indexación o de cobertura.
  • [ ] Sube de nuevo el TTL de DNS a 3600.
  • [ ] Verifica que el primer respaldo automático se generó y llegó al almacenamiento externo.
  • [ ] Entrega al cliente la documentación: accesos, cómo editar contenido, a quién llamar si algo falla y qué incluye el mantenimiento (ver Entrega y capacitación).
  • [ ] Pide testimonio y permiso para incluir el proyecto en tu portafolio.

Plantilla reutilizable

Copia esta checklist a Notion, Trello o un issue de GitHub con casillas. Cada proyecto que lances la hará más completa y cada vez lanzarás más rápido y con menos estrés.

Error común

Lanzar un viernes por la tarde. Si algo falla, el fin de semana se arruina (el tuyo y el del cliente). Lanza de martes a jueves por la mañana.

Ejercicios ​

  1. Adapta la arquitectura del escenario para un cliente con presupuesto mínimo y un sitio solo informativo con formulario de contacto.
  2. Escribe los registros DNS completos para el escenario (VPS 203.0.113.10, correo en Google Workspace) incluyendo SPF y DMARC.
  3. Después del lanzamiento el sitio no aparece en Google tras dos semanas. Enumera qué revisarías.
Ver solución
  1. Sitio estático en Netlify/Cloudflare Pages (gratis o casi), formulario con Netlify Forms o una función serverless, dominio con DNS en Cloudflare, correo con el proveedor que el cliente prefiera, monitoreo con UptimeRobot. Sin VPS que mantener.
txt
A      @        203.0.113.10
CNAME  www      clinicasonrisa.com.
MX     @        1 smtp.google.com.
TXT    @        "v=spf1 include:_spf.google.com ~all"
TXT    google._domainkey  "v=DKIM1; k=rsa; p=...(lo genera Google Workspace)"
TXT    _dmarc   "v=DMARC1; p=quarantine; rua=mailto:dmarc@clinicasonrisa.com"
  1. robots.txt (que no tenga Disallow: /), etiquetas noindex olvidadas, Search Console verificado y sitemap enviado, errores de cobertura en Search Console, que las páginas respondan 200 (no redirecciones en bucle) y que el contenido sea útil y con títulos únicos.

Resumen ​

  • Lanzar es un proceso: cuentas, servidor, secretos, seguridad, correo, SEO, despliegue, respaldos, monitoreo.
  • Cuentas y dominio a nombre del cliente, con 2FA y gestor de contraseñas.
  • Revisa robots.txt, .env inaccesible, HTTPS, SPF/DKIM/DMARC y formularios funcionando.
  • Baja el TTL antes, verifica con un script después y vigila la primera semana.
  • Documenta y entrega: ese cierre profesional es lo que te consigue el siguiente cliente.
  • Continúa con Trabajo con clientes para dominar la parte de negocio.

Academia Web — aprende, copia y construye.