Meraki Developer
Volver al Blog Tecnologías Web Modernas para Empresas en California: Stack Tecnológico 2025

Tecnologías Web Modernas para Empresas en California: Stack Tecnológico 2025

Gerardo Martínez

California lidera la innovación tecnológica mundial, y las empresas que operan aquí necesitan adoptar las tecnologías web más avanzadas para mantenerse competitivas. Con Silicon Valley marcando las tendencias globales, elegir el stack tecnológico correcto puede determinar el éxito o fracaso de tu proyecto digital.

El Ecosistema Tecnológico de California

Estadísticas del Mercado Tech

  • $1.8 billones en valor de empresas tech en California
  • 3.8 millones de trabajadores en tecnología
  • 40% de startups globales tienen sede en California
  • $180 mil salario promedio de desarrolladores senior

Tendencias Tecnológicas Dominantes

Frameworks en Crecimiento:

  • React: 68% de adopción en startups californianas
  • Next.js: 45% crecimiento año tras año
  • Vue.js: Preferido por 32% de empresas medianas
  • Svelte: Adoptado por 28% de nuevos proyectos

Frontend: Frameworks y Librerías

1. React - El Estándar de la Industria

¿Por qué React domina en California?

  • Respaldado por Meta (Facebook)
  • Ecosistema maduro y extenso
  • Comunidad activa en Silicon Valley
  • Facilidad para encontrar desarrolladores

Ejemplo de Componente React:

import React, { useState, useEffect } from "react";
import styled from "styled-components";

const BusinessCard = styled.div`
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  padding: 2rem;
  transition: transform 0.3s ease;

  &:hover {
    transform: translateY(-5px);
  }
`;

const CaliforniaBusinessComponent = () => {
  const [businesses, setBusinesses] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchCaliforniaBusinesses();
  }, []);

  const fetchCaliforniaBusinesses = async () => {
    try {
      const response = await fetch("/api/businesses/california");
      const data = await response.json();
      setBusinesses(data);
    } catch (error) {
      console.error("Error fetching businesses:", error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Cargando negocios en California...</div>;

  return (
    <div className="business-grid">
      {businesses.map((business) => (
        <BusinessCard key={business.id}>
          <h3>{business.name}</h3>
          <p>{business.location}</p>
          <span>{business.category}</span>
        </BusinessCard>
      ))}
    </div>
  );
};

export default CaliforniaBusinessComponent;

Ventajas para Empresas Californianas:

  • Desarrollo rápido de MVPs
  • Escalabilidad probada (Netflix, Airbnb, Uber)
  • Integración fácil con APIs
  • SEO mejorado con Next.js

2. Next.js - Full-Stack React Framework

Características Clave:

// pages/api/california-businesses.js
export default async function handler(req, res) {
  if (req.method === 'GET') {
    try {
      const businesses = await getCaliforniaBusinesses();
      res.status(200).json(businesses);
    } catch (error) {
      res.status(500).json({ error: 'Error fetching businesses' });
    }
  }
}

// pages/businesses/[city].js
import { useRouter } from 'next/router';
import { GetStaticProps, GetStaticPaths } from 'next';

export default function CityBusinesses({ businesses, city }) {
  const router = useRouter();

  return (
    <div>
      <h1>Negocios en {city}, California</h1>
      {businesses.map(business => (
        <BusinessCard key={business.id} business={business} />
      ))}
    </div>
  );
}

export const getStaticPaths = async () => {
  const cities = ['san-diego', 'los-angeles', 'san-francisco'];
  const paths = cities.map(city => ({ params: { city } }));

  return { paths, fallback: false };
};

export const getStaticProps = async ({ params }) => {
  const businesses = await getBusinessesByCity(params.city);

  return {
    props: { businesses, city: params.city },
    revalidate: 3600 // Regenerar cada hora
  };
};

3. Vue.js - Progresivo y Flexible

Composición API en Vue 3:

<template>
  <div class="california-dashboard">
    <h2>Dashboard de Negocios - California</h2>

    <div class="filters">
      <select v-model="selectedCity" @change="filterBusinesses">
        <option value="">Todas las ciudades</option>
        <option v-for="city in cities" :key="city" :value="city">
          {{ city }}
        </option>
      </select>
    </div>

    <div class="business-grid">
      <BusinessCard
        v-for="business in filteredBusinesses"
        :key="business.id"
        :business="business"
        @click="selectBusiness"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from "vue";
import { useBusinessStore } from "@/stores/business";

const businessStore = useBusinessStore();
const selectedCity = ref("");
const cities = ref(["San Diego", "Los Angeles", "San Francisco"]);

const filteredBusinesses = computed(() => {
  if (!selectedCity.value) return businessStore.businesses;
  return businessStore.businesses.filter(
    (business) => business.city === selectedCity.value
  );
});

const filterBusinesses = () => {
  businessStore.filterByCity(selectedCity.value);
};

const selectBusiness = (business) => {
  businessStore.setSelectedBusiness(business);
};

onMounted(() => {
  businessStore.fetchCaliforniaBusinesses();
});
</script>

4. Svelte/SvelteKit - Performance Extremo

Componente Svelte Optimizado:

<script>
  import { onMount } from 'svelte';
  import { fade, fly } from 'svelte/transition';

  let businesses = [];
  let loading = true;
  let selectedCategory = '';

  $: filteredBusinesses = selectedCategory
    ? businesses.filter(b => b.category === selectedCategory)
    : businesses;

  onMount(async () => {
    const response = await fetch('/api/california-businesses');
    businesses = await response.json();
    loading = false;
  });

  function handleCategoryChange(event) {
    selectedCategory = event.target.value;
  }
</script>

<div class="california-business-finder">
  <h1>Encuentra Negocios en California</h1>

  {#if loading}
    <div class="loading" transition:fade>
      Cargando negocios...
    </div>
  {:else}
    <select bind:value={selectedCategory} on:change={handleCategoryChange}>
      <option value="">Todas las categorías</option>
      <option value="tech">Tecnología</option>
      <option value="retail">Retail</option>
      <option value="food">Restaurantes</option>
    </select>

    <div class="business-grid">
      {#each filteredBusinesses as business (business.id)}
        <div
          class="business-card"
          transition:fly="{{ y: 20, duration: 300 }}"
        >
          <h3>{business.name}</h3>
          <p>{business.description}</p>
          <span class="location">{business.city}, CA</span>
        </div>
      {/each}
    </div>
  {/if}
</div>

<style>
  .california-business-finder {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }

  .business-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.5rem;
    margin-top: 2rem;
  }

  .business-card {
    background: white;
    border-radius: 8px;
    padding: 1.5rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    transition: transform 0.2s ease;
  }

  .business-card:hover {
    transform: translateY(-2px);
  }
</style>

Backend: Tecnologías del Servidor

1. Node.js - JavaScript Everywhere

API REST con Express:

const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");

const app = express();

// Middleware de seguridad
app.use(helmet());
app.use(
  cors({
    origin: process.env.FRONTEND_URL,
    credentials: true,
  })
);

// Rate limiting para APIs
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100, // máximo 100 requests por IP
});
app.use("/api/", limiter);

// Middleware
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true }));

// Rutas para negocios en California
app.get("/api/businesses/california", async (req, res) => {
  try {
    const { city, category, limit = 20 } = req.query;

    const businesses = await Business.find({
      state: "California",
      ...(city && { city }),
      ...(category && { category }),
    })
      .limit(parseInt(limit))
      .sort({ createdAt: -1 });

    res.json({
      success: true,
      data: businesses,
      total: businesses.length,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: "Error fetching California businesses",
    });
  }
});

// Crear nuevo negocio
app.post("/api/businesses", async (req, res) => {
  try {
    const businessData = req.body;

    // Validación específica para California
    if (businessData.state !== "California") {
      return res.status(400).json({
        success: false,
        error: "Solo aceptamos negocios de California",
      });
    }

    const business = new Business(businessData);
    await business.save();

    res.status(201).json({
      success: true,
      data: business,
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message,
    });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Servidor corriendo en puerto ${PORT}`);
});

2. Python con FastAPI

API Moderna con Python:

from fastapi import FastAPI, HTTPException, Depends, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional
import asyncio
from datetime import datetime

app = FastAPI(
    title="California Business API",
    description="API para gestión de negocios en California",
    version="1.0.0"
)

# CORS para frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Modelos Pydantic
class BusinessBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    description: str = Field(..., max_length=500)
    city: str = Field(..., regex="^(San Diego|Los Angeles|San Francisco|Sacramento)$")
    category: str
    website: Optional[str] = None
    phone: Optional[str] = None

class BusinessCreate(BusinessBase):
    pass

class Business(BusinessBase):
    id: int
    created_at: datetime
    updated_at: datetime

    class Config:
        orm_mode = True

# Endpoints
@app.get("/api/businesses/california", response_model=List[Business])
async def get_california_businesses(
    city: Optional[str] = Query(None, description="Filtrar por ciudad"),
    category: Optional[str] = Query(None, description="Filtrar por categoría"),
    limit: int = Query(20, ge=1, le=100, description="Límite de resultados")
):
    """
    Obtener negocios en California con filtros opcionales
    """
    try:
        # Simulación de consulta a base de datos
        businesses = await get_businesses_from_db(
            city=city,
            category=category,
            limit=limit
        )
        return businesses
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/businesses", response_model=Business)
async def create_business(business: BusinessCreate):
    """
    Crear un nuevo negocio en California
    """
    try:
        # Validación adicional
        if business.city not in ["San Diego", "Los Angeles", "San Francisco", "Sacramento"]:
            raise HTTPException(
                status_code=400,
                detail="Solo aceptamos negocios en ciudades principales de California"
            )

        new_business = await create_business_in_db(business)
        return new_business
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/api/businesses/{business_id}", response_model=Business)
async def get_business(business_id: int):
    """
    Obtener un negocio específico por ID
    """
    business = await get_business_by_id(business_id)
    if not business:
        raise HTTPException(status_code=404, detail="Negocio no encontrado")
    return business

# Funciones auxiliares (simuladas)
async def get_businesses_from_db(city=None, category=None, limit=20):
    # Simulación de consulta asíncrona a base de datos
    await asyncio.sleep(0.1)
    return []

async def create_business_in_db(business_data):
    # Simulación de creación en base de datos
    await asyncio.sleep(0.1)
    return business_data

async def get_business_by_id(business_id):
    # Simulación de consulta por ID
    await asyncio.sleep(0.1)
    return None

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

3. Go - Performance y Concurrencia

API en Go con Gin:

package main

import (
    "net/http"
    "strconv"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-contrib/cors"
)

type Business struct {
    ID          int       `json:"id"`
    Name        string    `json:"name" binding:"required"`
    Description string    `json:"description"`
    City        string    `json:"city" binding:"required"`
    Category    string    `json:"category"`
    Website     string    `json:"website"`
    Phone       string    `json:"phone"`
    CreatedAt   time.Time `json:"created_at"`
}

type BusinessService struct {
    businesses []Business
}

func NewBusinessService() *BusinessService {
    return &BusinessService{
        businesses: make([]Business, 0),
    }
}

func (bs *BusinessService) GetCaliforniaBusinesses(city, category string, limit int) []Business {
    var filtered []Business

    for _, business := range bs.businesses {
        if city != "" && business.City != city {
            continue
        }
        if category != "" && business.Category != category {
            continue
        }
        filtered = append(filtered, business)

        if len(filtered) >= limit {
            break
        }
    }

    return filtered
}

func (bs *BusinessService) CreateBusiness(business Business) Business {
    business.ID = len(bs.businesses) + 1
    business.CreatedAt = time.Now()
    bs.businesses = append(bs.businesses, business)
    return business
}

func main() {
    r := gin.Default()

    // CORS middleware
    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"http://localhost:3000"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour,
    }))

    businessService := NewBusinessService()

    // Rutas API
    api := r.Group("/api")
    {
        api.GET("/businesses/california", func(c *gin.Context) {
            city := c.Query("city")
            category := c.Query("category")
            limitStr := c.DefaultQuery("limit", "20")

            limit, err := strconv.Atoi(limitStr)
            if err != nil {
                limit = 20
            }

            businesses := businessService.GetCaliforniaBusinesses(city, category, limit)

            c.JSON(http.StatusOK, gin.H{
                "success": true,
                "data":    businesses,
                "total":   len(businesses),
            })
        })

        api.POST("/businesses", func(c *gin.Context) {
            var business Business

            if err := c.ShouldBindJSON(&business); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{
                    "success": false,
                    "error":   err.Error(),
                })
                return
            }

            // Validación para California
            validCities := map[string]bool{
                "San Diego":     true,
                "Los Angeles":   true,
                "San Francisco": true,
                "Sacramento":    true,
            }

            if !validCities[business.City] {
                c.JSON(http.StatusBadRequest, gin.H{
                    "success": false,
                    "error":   "Solo aceptamos negocios en ciudades principales de California",
                })
                return
            }

            newBusiness := businessService.CreateBusiness(business)

            c.JSON(http.StatusCreated, gin.H{
                "success": true,
                "data":    newBusiness,
            })
        })
    }

    r.Run(":8080")
}

Bases de Datos Modernas

1. PostgreSQL - Robustez Empresarial

Schema para Negocios Californianos:

-- Extensiones útiles
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "postgis";

-- Tabla de negocios
CREATE TABLE california_businesses (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    city VARCHAR(50) NOT NULL,
    state VARCHAR(2) DEFAULT 'CA',
    zip_code VARCHAR(10),
    category VARCHAR(50),
    website VARCHAR(255),
    phone VARCHAR(20),
    email VARCHAR(100),
    location POINT, -- Para coordenadas geográficas
    rating DECIMAL(3,2) DEFAULT 0.00,
    review_count INTEGER DEFAULT 0,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Índices para optimización
CREATE INDEX idx_california_businesses_city ON california_businesses(city);
CREATE INDEX idx_california_businesses_category ON california_businesses(category);
CREATE INDEX idx_california_businesses_rating ON california_businesses(rating DESC);
CREATE INDEX idx_california_businesses_location ON california_businesses USING GIST(location);

-- Trigger para updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_california_businesses_updated_at
    BEFORE UPDATE ON california_businesses
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- Función para buscar negocios por proximidad
CREATE OR REPLACE FUNCTION find_nearby_businesses(
    lat DECIMAL,
    lng DECIMAL,
    radius_km INTEGER DEFAULT 10,
    business_category VARCHAR DEFAULT NULL
)
RETURNS TABLE (
    id UUID,
    name VARCHAR,
    city VARCHAR,
    category VARCHAR,
    distance_km DECIMAL
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        b.id,
        b.name,
        b.city,
        b.category,
        ROUND(
            (ST_Distance(
                ST_GeogFromText('POINT(' || lng || ' ' || lat || ')'),
                ST_GeogFromText('POINT(' || ST_X(b.location) || ' ' || ST_Y(b.location) || ')')
            ) / 1000)::DECIMAL, 2
        ) as distance_km
    FROM california_businesses b
    WHERE
        b.is_active = true
        AND (business_category IS NULL OR b.category = business_category)
        AND ST_DWithin(
            ST_GeogFromText('POINT(' || lng || ' ' || lat || ')'),
            ST_GeogFromText('POINT(' || ST_X(b.location) || ' ' || ST_Y(b.location) || ')'),
            radius_km * 1000
        )
    ORDER BY distance_km;
END;
$$ LANGUAGE plpgsql;

2. MongoDB - Flexibilidad NoSQL

Schema y Consultas MongoDB:

// Modelo de negocio con Mongoose
const mongoose = require("mongoose");

const businessSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
      trim: true,
      maxlength: 100,
    },
    description: {
      type: String,
      maxlength: 1000,
    },
    location: {
      type: {
        type: String,
        enum: ["Point"],
        default: "Point",
      },
      coordinates: {
        type: [Number], // [longitude, latitude]
        required: true,
      },
      address: {
        street: String,
        city: {
          type: String,
          required: true,
          enum: ["San Diego", "Los Angeles", "San Francisco", "Sacramento"],
        },
        state: {
          type: String,
          default: "CA",
        },
        zipCode: String,
      },
    },
    category: {
      type: String,
      required: true,
      enum: [
        "technology",
        "retail",
        "food",
        "healthcare",
        "professional-services",
      ],
    },
    contact: {
      website: String,
      phone: String,
      email: String,
      socialMedia: {
        facebook: String,
        instagram: String,
        linkedin: String,
      },
    },
    businessHours: [
      {
        day: {
          type: String,
          enum: [
            "monday",
            "tuesday",
            "wednesday",
            "thursday",
            "friday",
            "saturday",
            "sunday",
          ],
        },
        open: String,
        close: String,
        isClosed: {
          type: Boolean,
          default: false,
        },
      },
    ],
    rating: {
      average: {
        type: Number,
        default: 0,
        min: 0,
        max: 5,
      },
      count: {
        type: Number,
        default: 0,
      },
    },
    features: [String], // ['parking', 'wifi', 'wheelchair-accessible']
    isActive: {
      type: Boolean,
      default: true,
    },
    verificationStatus: {
      type: String,
      enum: ["pending", "verified", "rejected"],
      default: "pending",
    },
  },
  {
    timestamps: true,
  }
);

// Índice geoespacial para búsquedas por proximidad
businessSchema.index({ location: "2dsphere" });
businessSchema.index({ category: 1, "location.address.city": 1 });
businessSchema.index({ "rating.average": -1 });

// Método para encontrar negocios cercanos
businessSchema.statics.findNearby = function (
  longitude,
  latitude,
  maxDistance = 10000
) {
  return this.find({
    location: {
      $near: {
        $geometry: {
          type: "Point",
          coordinates: [longitude, latitude],
        },
        $maxDistance: maxDistance, // metros
      },
    },
    isActive: true,
  });
};

// Método para buscar por ciudad y categoría
businessSchema.statics.findByCityAndCategory = function (
  city,
  category,
  options = {}
) {
  const query = {
    "location.address.city": city,
    isActive: true,
  };

  if (category) {
    query.category = category;
  }

  return this.find(query)
    .sort(options.sort || { "rating.average": -1 })
    .limit(options.limit || 20)
    .skip(options.skip || 0);
};

const Business = mongoose.model("Business", businessSchema);

// Ejemplos de uso
async function getCaliforniaBusinesses() {
  // Buscar negocios en San Diego
  const sanDiegoBusinesses = await Business.findByCityAndCategory("San Diego");

  // Buscar restaurantes cerca de una ubicación
  const nearbyRestaurants = await Business.findNearby(-117.1611, 32.7157, 5000)
    .where("category")
    .equals("food");

  // Agregación para estadísticas por ciudad
  const cityStats = await Business.aggregate([
    { $match: { isActive: true } },
    {
      $group: {
        _id: "$location.address.city",
        totalBusinesses: { $sum: 1 },
        averageRating: { $avg: "$rating.average" },
        categories: { $addToSet: "$category" },
      },
    },
    { $sort: { totalBusinesses: -1 } },
  ]);

  return { sanDiegoBusinesses, nearbyRestaurants, cityStats };
}

DevOps y Deployment

1. Docker para Containerización

Dockerfile para aplicación Node.js:

# Multi-stage build para optimización
FROM node:18-alpine AS builder

WORKDIR /app

# Copiar package files
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Copiar código fuente
COPY . .

# Build de la aplicación
RUN npm run build

# Imagen de producción
FROM node:18-alpine AS production

# Crear usuario no-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

WORKDIR /app

# Copiar archivos necesarios desde builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

# Cambiar ownership
RUN chown -R nextjs:nodejs /app
USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV NODE_ENV production

CMD ["node", "server.js"]

Docker Compose para desarrollo:

version: "3.8"

services:
  # Frontend Next.js
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - NEXT_PUBLIC_API_URL=http://localhost:8000
    depends_on:
      - backend

  # Backend API
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    volumes:
      - ./backend:/app
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:password@postgres:5432/california_businesses
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis

  # Base de datos PostgreSQL
  postgres:
    image: postgres:15-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=california_businesses
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql

  # Redis para caché
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  # Nginx como reverse proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - frontend
      - backend

volumes:
  postgres_data:
  redis_data:

2. CI/CD con GitHub Actions

Workflow para deployment:

name: Deploy to California Servers

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: "18"
  REGISTRY: ghcr.io
  IMAGE_NAME: california-business-app

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test_db
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run linting
        run: npm run lint

      - name: Run type checking
        run: npm run type-check

      - name: Run tests
        run: npm run test:ci
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix={{branch}}-

      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to California servers
        uses: appleboy/ssh-action@v0.1.5
        with:
          host: ${{ secrets.CALIFORNIA_SERVER_HOST }}
          username: ${{ secrets.CALIFORNIA_SERVER_USER }}
          key: ${{ secrets.CALIFORNIA_SERVER_SSH_KEY }}
          script: |
            cd /opt/california-business-app
            docker-compose pull
            docker-compose up -d
            docker system prune -f

      - name: Health check
        run: |
          sleep 30
          curl -f ${{ secrets.CALIFORNIA_APP_URL }}/health || exit 1

      - name: Notify deployment
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          channel: "#deployments"
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
        if: always()

Casos de Éxito con Stack Moderno

Startup de Delivery - San Francisco

Stack Tecnológico:

  • Frontend: Next.js + TypeScript + Tailwind CSS
  • Backend: Node.js + Express + PostgreSQL
  • Mobile: React Native
  • Infrastructure: AWS + Docker + Kubernetes

Resultados:

  • 0 a 100,000 usuarios en 6 meses
  • 99.9% uptime
  • < 200ms tiempo de respuesta API
  • Escalamiento automático durante picos

E-commerce B2B - Los Angeles

Stack Tecnológico:

  • Frontend: Vue.js + Nuxt.js + Vuetify
  • Backend: Python + FastAPI + MongoDB
  • Search: Elasticsearch
  • Infrastructure: Google Cloud + Terraform

Resultados:

  • $50M en transacciones anuales
  • 500,000 productos en catálogo
  • Búsqueda en < 50ms
  • 40% reducción en costos de infraestructura

Plataforma SaaS - San Diego

Stack Tecnológico:

  • Frontend: React + TypeScript + Material-UI
  • Backend: Go + PostgreSQL + Redis
  • Real-time: WebSockets + Socket.io
  • Infrastructure: Digital Ocean + Docker Swarm

Resultados:

  • 10,000+ empresas usuarias
  • 99.95% disponibilidad
  • Procesamiento de 1M+ eventos/día
  • Escalamiento global a 15 países

Herramientas de Desarrollo

1. IDEs y Editores

Visual Studio Code - Configuración Optimizada:

{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  },
  "typescript.preferences.importModuleSpecifier": "relative",
  "emmet.includeLanguages": {
    "javascript": "javascriptreact",
    "typescript": "typescriptreact"
  },
  "extensions.recommendations": [
    "bradlc.vscode-tailwindcss",
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "ms-vscode.vscode-typescript-next",
    "formulahendry.auto-rename-tag",
    "christian-kohler.path-intellisense"
  ]
}

2. Herramientas de Testing

Jest + Testing Library:

// __tests__/CaliforniaBusinesses.test.jsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { setupServer } from "msw/node";
import CaliforniaBusinesses from "../components/CaliforniaBusinesses";

// Mock del servidor API
const server = setupServer(
  rest.get("/api/businesses/california", (req, res, ctx) => {
    const city = req.url.searchParams.get("city");

    const mockBusinesses = [
      {
        id: 1,
        name: "Tech Startup SD",
        city: "San Diego",
        category: "technology",
      },
      {
        id: 2,
        name: "LA Restaurant",
        city: "Los Angeles",
        category: "food",
      },
    ];

    const filtered = city
      ? mockBusinesses.filter((b) => b.city === city)
      : mockBusinesses;

    return res(ctx.json(filtered));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("CaliforniaBusinesses Component", () => {
  test("renders business list correctly", async () => {
    render(<CaliforniaBusinesses />);

    // Verificar que se muestran los negocios
    await waitFor(() => {
      expect(screen.getByText("Tech Startup SD")).toBeInTheDocument();
      expect(screen.getByText("LA Restaurant")).toBeInTheDocument();
    });
  });

  test("filters businesses by city", async () => {
    const user = userEvent.setup();
    render(<CaliforniaBusinesses />);

    // Seleccionar filtro de ciudad
    const citySelect = screen.getByLabelText(/ciudad/i);
    await user.selectOptions(citySelect, "San Diego");

    // Verificar filtrado
    await waitFor(() => {
      expect(screen.getByText("Tech Startup SD")).toBeInTheDocument();
      expect(screen.queryByText("LA Restaurant")).not.toBeInTheDocument();
    });
  });

  test("handles API errors gracefully", async () => {
    // Simular error del servidor
    server.use(
      rest.get("/api/businesses/california", (req, res, ctx) => {
        return res(ctx.status(500), ctx.json({ error: "Server error" }));
      })
    );

    render(<CaliforniaBusinesses />);

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

Próximos Pasos

Las tecnologías web modernas evolucionan constantemente, especialmente en California donde la innovación nunca se detiene. Elegir el stack tecnológico correcto puede determinar el éxito de tu proyecto digital.

¿Necesitas ayuda para elegir las mejores tecnologías para tu proyecto? Solicita una consulta técnica gratuita y descubre cómo podemos ayudarte a construir soluciones web de clase mundial con las tecnologías más avanzadas.


¿Qué tecnologías web estás considerando para tu próximo proyecto? Comparte tu experiencia en los comentarios.