Use Cases

Claude Code × GCP Cloud Functions Guía Completa | Desarrollo Serverless Ultrarrápido

Optimiza GCP Cloud Functions con Claude Code. Implementa triggers HTTP/Pub/Sub/Firestore, pruebas locales y automatización de despliegues con ejemplos de código reales de la experiencia de Masa.

Cloud Run era excesivo — por eso elegí Cloud Functions

Soy Masa, desarrollador de claudecode-lab.com.

Cuando empecé a usar GCP Serverless, consideré Cloud Run. Flexible y basado en contenedores, pero para tareas como “solo recibir un webhook” o “ejecutar un batch una vez por la noche”, era claramente sobredimensionado. Ahí fue cuando cambié a Cloud Functions Gen2.

Con Claude Code, más del 90% de mi código de Cloud Functions está generado automáticamente. Me concentro en la revisión y validación.


Paso 1: Función con Trigger HTTP

import { http, HttpFunction } from "@google-cloud/functions-framework";
import { Request, Response } from "express";
import { Firestore } from "@google-cloud/firestore";

const db = new Firestore();
const VALID_TOKEN = process.env.API_SECRET_TOKEN;

interface ActionRequest {
  userId: string;
  action: string;
}

const handleAction: HttpFunction = async (req: Request, res: Response) => {
  // Configurar headers CORS
  res.set("Access-Control-Allow-Origin", "*");
  if (req.method === "OPTIONS") {
    res.set("Access-Control-Allow-Methods", "POST");
    res.set("Access-Control-Allow-Headers", "Authorization, Content-Type");
    res.status(204).send("");
    return;
  }

  // Solo permitir POST
  if (req.method !== "POST") {
    res.status(405).json({ success: false, message: "Method Not Allowed" });
    return;
  }

  // Validar Bearer token
  const authHeader = req.headers.authorization ?? "";
  if (!authHeader.startsWith("Bearer ") || authHeader.slice(7) !== VALID_TOKEN) {
    res.status(401).json({ success: false, message: "No autorizado" });
    return;
  }

  const body = req.body as Partial<ActionRequest>;
  if (!body.userId || !body.action) {
    res.status(400).json({ success: false, message: "userId y action son requeridos" });
    return;
  }

  try {
    const logRef = await db.collection("action_logs").add({
      userId: body.userId,
      action: body.action,
      timestamp: new Date(),
      ip: req.ip,
    });
    res.status(200).json({ success: true, message: "Acción registrada", logId: logRef.id });
  } catch (err) {
    console.error("Error al escribir en Firestore:", err);
    res.status(500).json({ success: false, message: "Error interno del servidor" });
  }
};

http("handleAction", handleAction);

Paso 2: Función con Trigger Pub/Sub

import { cloudEvent, CloudEvent } from "@google-cloud/functions-framework";
import { MessagePublishedData } from "@google/events/cloud/pubsub/v1/MessagePublishedData";
import { Storage } from "@google-cloud/storage";

const storage = new Storage();

interface ImageUploadedMessage {
  bucketName: string;
  filePath: string;
}

cloudEvent<MessagePublishedData>("handleImageUploaded", async (event: CloudEvent<MessagePublishedData>) => {
  const base64Data = event.data?.message?.data;
  if (!base64Data) {
    console.warn("No se recibieron datos del mensaje, omitiendo");
    return;
  }

  const rawJson = Buffer.from(base64Data, "base64").toString("utf-8");
  let payload: ImageUploadedMessage;
  try {
    payload = JSON.parse(rawJson) as ImageUploadedMessage;
  } catch {
    console.error("JSON inválido en mensaje Pub/Sub:", rawJson);
    return;
  }

  const { bucketName, filePath } = payload;
  try {
    const [metadata] = await storage.bucket(bucketName).file(filePath).getMetadata();
    console.log("Metadatos del archivo:", { nombre: metadata.name, tamaño: metadata.size });
  } catch (err) {
    // Lanzar excepción hace que Pub/Sub reintente el mensaje
    console.error(`Error al procesar ${filePath}:`, err);
    throw err;
  }
});

Paso 3: Función con Trigger Firestore

import { onDocumentWritten, Change, FirestoreEvent } from "firebase-functions/v2/firestore";
import { QueryDocumentSnapshot } from "firebase-admin/firestore";
import * as admin from "firebase-admin";

admin.initializeApp();
const db = admin.firestore();

export const onUserWrite = onDocumentWritten(
  "users/{userId}",
  async (event: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, { userId: string }>) => {
    const userId = event.params.userId;
    const before = event.data?.before;
    const after = event.data?.after;

    // Evento de creación
    if (!before?.exists && after?.exists) {
      const userData = after.data();
      await db.collection("email_queue").add({
        to: userData?.email,
        template: "welcome",
        createdAt: admin.firestore.FieldValue.serverTimestamp(),
        status: "pendiente",
      });
      return;
    }

    // Evento de eliminación — copiar al archivo
    if (before?.exists && !after?.exists) {
      await db.collection("deleted_users").doc(userId).set({
        ...before.data(),
        deletedAt: admin.firestore.FieldValue.serverTimestamp(),
      });
    }
  }
);

Paso 4: Pruebas Locales y Despliegue

# Inicio local
npm run build
npx @google-cloud/functions-framework --target=handleAction --port=8080

# Enviar solicitud de prueba
curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer token-de-prueba" \
  -d '{"userId": "user-123", "action": "login"}'

# Despliegue en GCP
gcloud functions deploy handleAction \
  --gen2 \
  --runtime=nodejs22 \
  --region=us-central1 \
  --source=. \
  --entry-point=handleAction \
  --trigger-http \
  --allow-unauthenticated \
  --memory=512Mi \
  --timeout=60s \
  --set-secrets="API_SECRET_TOKEN=api-secret-token:latest"

4 Errores Comunes

ErrorProblemaSolución
Cold StartLatencia de 2–5 s con instancias inactivas--min-instances=1 para funciones críticas
Límite de timeoutMáximo 9 minutos (540 s)Dividir tareas grandes con Pub/Sub
Permisos Secret ManagerPermission denied en tiempo de ejecuciónOtorgar rol secretmanager.secretAccessor
Falta de memoriaOOM con procesamiento pesadoConfigurar --memory=2Gi --cpu=2

Resumen

TriggerCaso de UsoConsideración Principal
HTTPWebhooks, endpoints APIAutenticación, CORS
Pub/SubProcesamiento asíncrono de eventosDecodificación base64, diseño de reintentos
FirestoreReaccionar a cambios de datosEvitar bucles infinitos
Cloud SchedulerBatch jobs programadosValidación OIDC, zona horaria

Artículos Relacionados

#claude-code #gcp #cloud-functions #typescript #serverless #pubsub
Gratis

PDF gratuito: Hoja de trucos de Claude Code en 5 minutos

Solo deja tu correo y te enviaremos al instante la hoja de trucos en una página A4.

Cuidamos tus datos personales y nunca enviamos spam.

Lleva tu flujo con Claude Code al siguiente nivel

50 plantillas de prompts probadas en producción, listas para copiar y pegar en Claude Code.

Masa

Sobre el autor

Masa

Ingeniero apasionado por Claude Code. Dirige claudecode-lab.com, un medio tecnológico en 10 idiomas con más de 2.000 páginas.