Blog

  • Go y PHP: cómo diseñar un sistema híbrido eficiente

    Go y PHP: cómo diseñar un sistema híbrido eficiente

    Este artículo describe una aproximación práctica para integrar Go y PHP en un sistema híbrido: cuándo delegar tareas, cómo comunicar microservicios y cómo desplegar con Docker.

    Incluye arquitectura, retos comunes, ejemplos de código (PHP, servicio en Go, Dockerfiles, docker-compose) y una checklist para implementación.

    Introducción

    Cuando una aplicación PHP comienza a mostrar cuellos de botella por procesamiento concurrente o tareas CPU-intensivas, una reescritura completa puede ser costosa y arriesgada.

    Una alternativa es un diseño híbrido: conservar PHP para la interfaz web y operaciones CRUD habituales, y delegar a Go las tareas de alto rendimiento y concurrencia.

    Prerrequisitos

    Antes de implementar la integración, confirma lo siguiente en tu entorno:

    • Servicio PHP con cliente HTTP (ej.: Guzzle) y ORM para la base de datos.
    • Servicio Go independiente para procesamiento concurrente.
    • Contenerización con Docker para ambos servicios y orquestación mínima (docker-compose o similar).
    • Definición clara de API (endpoints, formatos JSON, timeouts y reintentos).

    Desarrollo

    La pieza clave es separar responsabilidades y diseñar una comunicación eficiente entre servicios para evitar latencias y fallos de cascada.

    Arquitectura recomendada: microservicios independientes que intercambian JSON vía HTTP/REST. PHP se queda con las rutas web y consultas habituales; Go procesa trabajos intensivos y responde de forma asíncrona cuando sea pertinente.

    Procedimiento

    Pasos prácticos para integrar ambos servicios en producción:

    1. Identificar rutas y operaciones CPU-intensivas o altamente concurrentes que se puedan delegar a Go.
    2. Definir API REST clara entre PHP y Go con contrato JSON, códigos de estado y manejo de errores.
    3. Implementar timeouts y reintentos en el cliente HTTP de PHP; evitar bloquear las peticiones de usuario por procesos largos.
    4. Contenerizar ambos servicios y usar orquestación para escalado independiente.
    5. Monitorear latencia, errores y uso de recursos; ajustar goroutines y límites de concurrencia en Go.

    En entornos donde la respuesta inmediata no es requerida, considera que Go inicie el trabajo de forma asíncrona y devuelva un estado “aceptado” para que PHP continúe sin bloqueo.

    Ejemplos

    A continuación hay ejemplos extraídos de un caso real: cliente PHP (Guzzle), servicio en Go, Dockerfiles y docker-compose.

    Ejemplo: llamada desde PHP usando Guzzle (manejar timeout y convertir la respuesta JSON).

    <?php
    use GuzzleHttp\Client;
    $client = new Client();
    $response = $client->post('http://go-service/api/process', [
        'json' => ['data' => $data],
        'timeout' => 5,
    ]);
    $processedData = json_decode($response->getBody()->getContents(), true);
    Lenguaje del código: PHP (php)

    Servicio Go: endpoint que acepta JSON y lanza el procesamiento de forma concurrente. (Se muestra como texto plano para mantener compatibilidad del bloque.)

    package main
    import (
        "encoding/json"
        "fmt"
        "net/http"
        "time"
    )
    
    type RequestData struct {
        Data string `json:"data"`
    }
    
    func processData(w http.ResponseWriter, r *http.Request) {
        var reqData RequestData
        decoder := json.NewDecoder(r.Body)
        if err := decoder.Decode(&reqData); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        go func(data string) {
            time.Sleep(2 * time.Second) // Simula procesamiento
            fmt.Println("Processed data:", data)
        }(reqData.Data)
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(map[string]string{"status": "processing started"})
    }
    
    func main() {
        http.HandleFunc("/api/process", processData)
        http.ListenAndServe(":8080", nil)
    }
    Lenguaje del código: texto plano (plaintext)

    Dockerfile para el servicio PHP (ejemplo presente en la referencia):

    FROM php:8.0-apache
    COPY . /var/www/html/
    RUN docker-php-ext-install pdo pdo_mysql
    EXPOSE 80
    Lenguaje del código: Dockerfile (dockerfile)

    Dockerfile para el servicio Go (ejemplo presente en la referencia):

    FROM golang:1.19-alpine
    WORKDIR /app
    COPY . .
    RUN go build -o go-service
    CMD ["./go-service"]
    EXPOSE 8080
    Lenguaje del código: Dockerfile (dockerfile)

    docker-compose para levantar ambos servicios juntos:

    version: '3'
    services:
      php-service:
        build:
          context: ./php
        ports:
          - "8081:80"
      go-service:
        build:
          context: ./go
        ports:
          - "8080:8080"
    Lenguaje del código: YAML (yaml)

    Checklist

    1. Detectar operaciones costosas y definir contratos API para delegarlas.
    2. Implementar timeouts, reintentos y manejo de errores en PHP.
    3. Contenerizar servicios y configurar orquestación para escalado independiente.
    4. Monitorizar latencia, uso de CPU/memoria y ajustar concurrencia en Go.
    5. Probar fallos en la comunicación y definir fallbacks razonables en PHP.

    Conclusión

    Un enfoque híbrido Go + PHP permite aprovechar lo mejor de cada lenguaje sin rehacer todo el sistema. La clave es separar responsabilidades, diseñar APIs robustas y automatizar despliegues.

    Si decides seguir esta ruta, prioriza pruebas de integración, políticas de reintento y observabilidad para minimizar impactos en la experiencia de usuario.

  • Module Federation en Next.js: microfrontends prácticos

    Module Federation en Next.js: microfrontends prácticos

    Resumen: Este artículo explica cómo configurar Module Federation en Next.js para compartir componentes entre aplicaciones en tiempo de ejecución y crear micro‑frontends desplegables de forma independiente.

    Incluye pasos prácticos, ejemplos de configuración y código listo para copiar y adaptar en tus proyectos.

    Introducción

    Module Federation (a través del plugin de Webpack) permite compartir módulos entre aplicaciones en tiempo de ejecución en lugar de durante el build.

    En Next.js esto se usa para convertir un frontend monolítico en varios micro‑frontends que se desarrollan y despliegan de forma independiente, manteniendo una experiencia de usuario unificada.

    Prerrequisitos

    • Node.js y entorno para ejecutar aplicaciones Next.js.
    • Dos aplicaciones Next.js (por ejemplo: remote-app y host-app).
    • El paquete de integración para Next.js: @module-federation/nextjs-mf.

    Desarrollo

    Procedimiento

    Lista de pasos resumidos para crear el flujo host/remote:

    1. Crear la aplicación remote y añadir el plugin de Module Federation.
    2. Configurar next.config.js en la remote para exponer componentes.
    3. Crear los componentes que se van a exponer.
    4. Crear la aplicación host, añadir el plugin y configurar los remotes.
    5. Declarar tipos (si usas TypeScript) y consumir los componentes remotos con import dinámico y ssr: false.

    En las siguientes subsecciones se incluyen ejemplos mínimos de configuración y uso.

    Ejemplos

    Comandos iniciales para crear las aplicaciones (ejemplo):

    npx create-next-app@latest remote-app --typescript --tailwind --app
    cd remote-app
    npm install @module-federation/nextjs-mf
    npm run dev -- -p 3001
    Lenguaje del código: Bash (bash)

    Ejemplo: next.config.js de la aplicación remote (expone componentes):

    const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
    
    module.exports = {
      webpack(config, options) {
        config.plugins.push(
          new NextFederationPlugin({
            name: 'remote_app',
            filename: 'static/chunks/remoteEntry.js',
            exposes: {
              './Button': './components/Button',
              './UserCard': './components/UserCard',
            },
            shared: {
              react: { singleton: true },
              'react-dom': { singleton: true },
            },
          })
        );
        return config;
      },
    };
    Lenguaje del código: JavaScript (javascript)

    Componente Button en TypeScript (remote/components/Button.tsx):

    interface ButtonProps {
      onClick?: () => void;
      children: React.ReactNode;
      variant?: 'primary' | 'secondary';
    }
    
    export default function Button({ onClick, children, variant = 'primary' }: ButtonProps) {
      const baseClasses = 'px-4 py-2 rounded-md font-medium transition-colors';
      const variantClasses = {
        primary: 'bg-blue-500 text-white hover:bg-blue-600',
        secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
      };
    
      return (
        <button
          onClick={onClick}
          className={`${baseClasses} ${variantClasses[variant]}`}
        >
          {children}
        </button>
      );
    }
    Lenguaje del código: TypeScript (typescript)

    Arrancar la remote en el puerto 3001:

    npm run dev -- -p 3001
    Lenguaje del código: Bash (bash)

    Comandos y configuración para la aplicación host:

    npx create-next-app@latest host-app --typescript --tailwind --app
    cd host-app
    npm install @module-federation/nextjs-mf
    Lenguaje del código: Bash (bash)

    next.config.js de la aplicación host (consume la remote):

    const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
    
    module.exports = {
      webpack(config, options) {
        config.plugins.push(
          new NextFederationPlugin({
            name: 'host_app',
            remotes: {
              remote_app: 'remote_app@http://localhost:3001/_next/static/chunks/remoteEntry.js',
            },
            shared: {
              react: { singleton: true },
              'react-dom': { singleton: true },
            },
          })
        );
        return config;
      },
    };
    Lenguaje del código: JavaScript (javascript)

    Definiciones de tipos para TypeScript (types/remote-components.d.ts):

    declare module 'remote_app/Button' {
      interface ButtonProps {
        onClick?: () => void;
        children: React.ReactNode;
        variant?: 'primary' | 'secondary';
      }
      
      const Button: React.ComponentType<ButtonProps>;
      export default Button;
    }
    
    declare module 'remote_app/UserCard' {
      interface User {
        id: string;
        name: string;
        email: string;
        avatar?: string;
      }
      
      interface UserCardProps {
        user: User;
        onEdit?: () => void;
      }
    
      const UserCard: React.ComponentType<UserCardProps>;
      export default UserCard;
    }
    Lenguaje del código: TypeScript (typescript)

    Consumo dinámico de componentes remotos en app/page.tsx (host):

    'use client';
    
    import { Suspense, useState } from 'react';
    import dynamic from 'next/dynamic';
    
    const RemoteButton = dynamic(() => import('remote_app/Button'), {
      ssr: false,
    });
    const RemoteUserCard = dynamic(() => import('remote_app/UserCard'), {
      ssr: false,
    });
    
    export default function Home() {
      const [message, setMessage] = useState('');
    
      const sampleUser = {
        id: '1',
        name: 'John Doe',
        email: '[email protected]',
      };
    
      return (
        <div className="container mx-auto px-4 py-8">
          <h1 className="text-3xl font-bold text-center mb-8">
            Module Federation Demo
          </h1>
          
          <div className="max-w-2xl mx-auto space-y-8">
            <div className="text-center">
              <h2 className="text-xl font-semibold mb-4">Remote Button Component</h2>
              <Suspense fallback=<>Loading button...</>>
                <RemoteButton 
                  onClick={() => setMessage('Hello from remote button!')}
                  variant="primary"
                >
                  Click Me (Remote)
                </RemoteButton>
              </Suspense>
              {message && (
                <p className="mt-4 text-green-600 font-medium">{message}</p>
              )}
            </div>
    
            <div className="text-center">
              <h2 className="text-xl font-semibold mb-4">Remote UserCard Component</h2>
              <Suspense fallback=<>Loading user card...</>>
                <div className="flex justify-center">
                  <RemoteUserCard 
                    user={sampleUser}
                    onEdit={() => alert('Edit clicked!')}
                  />
                </div>
              </Suspense>
            </div>
          </div>
        </div>
      );
    }
    Lenguaje del código: TypeScript (typescript)

    Arrancar la host en otro puerto (ejemplo 3000):

    npm run dev -- -p 3000
    Lenguaje del código: Bash (bash)

    Nota práctica: siempre usar import dinámico con ssr: false y envolver en Suspense para evitar problemas con SSR.

    Ejemplo de manejo de URL remota entre entornos (ejemplo tomado como referencia):

    const remoteUrl = process.env.NODE_ENV === 'production' 
      ? 'https://your-host-app.vercel.app/_next/static/chunks/remoteEntry.js'
      : 'http://localhost:3000/_next/static/chunks/remoteEntry.js';
    Lenguaje del código: JavaScript (javascript)

    Checklist

    1. Usar import dinámico con ssr: false y envolver en Suspense.
    2. Compartir React y react-dom como singleton en la configuración de Module Federation.
    3. Declarar tipos .d.ts para los módulos federados si usas TypeScript.
    4. Probar en desarrollo con URLs locales y configurar la URL remota para producción mediante variables de entorno.
    5. Verificar que no se carguen múltiples instancias de React (comportamientos extraños en hooks).

    Estos pasos cubren las causas más comunes de errores y facilitan el diagnóstico durante la integración.

    Conclusión

    Module Federation en Next.js permite compartir componentes entre aplicaciones sin recompilar todo el ecosistema, lo que facilita equipos independientes y despliegues más rápidos.

    No obstante, añade complejidad: obliga a diseñar componentes para renderizado en cliente y a gestionar dependencias compartidas con cuidado.

    Si tu equipo gestiona varias aplicaciones relacionadas o necesitas un sistema de diseño compartido en tiempo real, Module Federation es una herramienta que vale la pena probar y dominar.

  • Escala Symfony con Memcached, múltiples PHP‑FPM y Nginx

    Escala Symfony con Memcached, múltiples PHP‑FPM y Nginx

    Resumen: configuración práctica para escalar una aplicación Symfony en Docker con Nginx como proxy, tres (o más) contenedores PHP‑FPM y Memcached para sesiones compartidas.

    El artículo describe la arquitectura, los archivos clave, comandos para levantar el entorno y pruebas para verificar persistencia de sesión entre contenedores.

    Introducción

    Cuando una aplicación Symfony supera la capacidad de un solo contenedor PHP‑FPM, la solución es ejecutar múltiples instancias de PHP‑FPM y balancearlas con Nginx. Para que las sesiones funcionen entre instancias se requiere un almacén centralizado: Memcached.

    Prerrequisitos

    Antes de empezar, confirma lo siguiente en tu entorno:

    1. Docker y Docker Compose instalados en la máquina host.
    2. Una aplicación Symfony ubicada en ./src dentro del proyecto.
    3. Conocimientos básicos de Nginx, PHP‑FPM y configuración de servicios en contenedores.

    Desarrollo

    La arquitectura recomendada mínima incluye: un contenedor Nginx (proxy/load‑balancer), tres contenedores PHP‑FPM idénticos, un contenedor Memcached y un contenedor PostgreSQL. Nginx distribuye peticiones entre los PHP‑FPM y Memcached almacena sesiones.

    project/
    ├── docker/
    │   ├── nginx/
    │   │   ├── Dockerfile
    │   │   └── default.conf
    │   ├── php-fpm/
    │   │   ├── Dockerfile
    │   │   └── php.ini
    │   └── memcached/
    │       └── memcached.conf
    ├── src/  (tu aplicación Symfony)
    ├── docker-compose.yml
    └── .env
    Lenguaje del código: texto plano (plaintext)

    Procedimiento

    Los pasos esenciales: definir docker-compose, configurar Nginx upstream, preparar la imagen PHP‑FPM con memcached y opcache, ajustar php.ini y la pool de PHP‑FPM, configurar Symfony para usar Memcached como handler de sesión y levantar el conjunto.

    version: '3.8'
    
    services:
      nginx:
        build:
          context: ./docker/nginx
          dockerfile: Dockerfile
        ports:
          - "80:80"
        volumes:
          - ./src:/var/www/symfony
          - php-fpm-sockets1:/var/run/sock/fpm1
          - php-fpm-sockets2:/var/run/sock/fpm2
          - php-fpm-sockets3:/var/run/sock/fpm3
        depends_on:
          - php-fpm-1
          - php-fpm-2
          - php-fpm-3
        networks:
          - symfony-network
    
      php-fpm-1:
        build:
          context: ./docker/php-fpm
          dockerfile: Dockerfile
        volumes:
          - ./src:/var/www/symfony
          - php-fpm-sockets1:/var/run/sock:rw
        environment:
          - APP_ENV=prod
          - DATABASE_URL=postgresql://symfony:secret@postgres:5432/symfony_db
          - MEMCACHED_HOST=memcached
          - MEMCACHED_PORT=11211
        depends_on:
          - memcached
          - postgres
        networks:
          - symfony-network
    
      php-fpm-2:
        build:
          context: ./docker/php-fpm
          dockerfile: Dockerfile
        volumes:
          - ./src:/var/www/symfony
          - php-fpm-sockets2:/var/run/sock:rw
        environment:
          - APP_ENV=prod
          - DATABASE_URL=postgresql://symfony:secret@postgres:5432/symfony_db
          - MEMCACHED_HOST=memcached
          - MEMCACHED_PORT=11211
        depends_on:
          - memcached
          - postgres
        networks:
          - symfony-network
    
      php-fpm-3:
        build:
          context: ./docker/php-fpm
          dockerfile: Dockerfile
        volumes:
          - ./src:/var/www/symfony
          - php-fpm-sockets3:/var/run/sock:rw
        environment:
          - APP_ENV=prod
          - DATABASE_URL=postgresql://symfony:secret@postgres:5432/symfony_db
          - MEMCACHED_HOST=memcached
          - MEMCACHED_PORT=11211
        depends_on:
          - memcached
          - postgres
        networks:
          - symfony-network
    
      memcached:
        image: memcached:1.6-alpine
        ports:
          - "11211:11211"
        command: memcached -m 256
        networks:
          - symfony-network
    
      postgres:
        image: postgres:15-alpine
        environment:
          - POSTGRES_DB=symfony_db
          - POSTGRES_USER=symfony
          - POSTGRES_PASSWORD=secret
        volumes:
          - postgres-data:/var/lib/postgresql/data
        networks:
          - symfony-network
    
    networks:
      symfony-network:
        driver: bridge
    
    volumes:
      postgres-data:
      php-fpm-sockets1:
      php-fpm-sockets2:
      php-fpm-sockets3:
    Lenguaje del código: YAML (yaml)
    FROM nginx:1.25-alpine
    
    COPY default.conf /etc/nginx/conf.d/default.conf
    RUN mkdir -p /var/www/symfony
    WORKDIR /var/www/symfony
    EXPOSE 80
    Lenguaje del código: Dockerfile (dockerfile)
    upstream php_fpm_backend {
        least_conn;
        server unix:/var/run/sock/fpm1/php-fpm.sock weight=1 max_fails=3 fail_timeout=30s;
        server unix:/var/run/sock/fpm2/php-fpm.sock weight=1 max_fails=3 fail_timeout=30s;
        server unix:/var/run/sock/fpm3/php-fpm.sock weight=1 max_fails=3 fail_timeout=30s;
    }
    
    server {
        listen 80;
        server_name localhost;
        root /var/www/symfony/public;
        index index.php;
        client_max_body_size 20M;
        client_body_buffer_size 128k;
    
        location / {
            try_files $uri /index.php$is_args$args;
        }
    
        location ~ ^/index\.php(/|$) {
            fastcgi_pass php_fpm_backend;
            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include fastcgi_params;
    
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            fastcgi_param DOCUMENT_ROOT $realpath_root;
    
            fastcgi_buffer_size 128k;
            fastcgi_buffers 4 256k;
            fastcgi_busy_buffers_size 256k;
            fastcgi_temp_file_write_size 256k;
    
            fastcgi_read_timeout 300;
            fastcgi_send_timeout 300;
    
            fastcgi_intercept_errors off;
            internal;
        }
    
        location ~ \.php$ {
            return 404;
        }
    
        error_log /var/log/nginx/symfony_error.log;
        access_log /var/log/nginx/symfony_access.log;
    }
    Lenguaje del código: Nginx (nginx)
    FROM php:8.2-fpm-alpine
    
    RUN apk add --no-cache \
        postgresql-dev \
        libzip-dev \
        libmemcached-dev \
        zlib-dev \
        cyrus-sasl-dev \
        git \
        unzip
    RUN docker-php-ext-install \
        pdo \
        pdo_pgsql \
        opcache \
        zip
    RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
        && pecl install memcached-3.2.0 \
        && docker-php-ext-enable memcached \
        && apk del .build-deps
    COPY php.ini /usr/local/etc/php/conf.d/custom.ini
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    WORKDIR /var/www/symfony
    RUN addgroup -g 1000 symfony && adduser -D -u 1000 -G symfony symfony
    RUN chown -R symfony:symfony /var/www/symfony
    USER symfony
    EXPOSE 9000
    CMD ["php-fpm"]
    Lenguaje del código: Dockerfile (dockerfile)
    [PHP]
    memory_limit = 512M
    upload_max_filesize = 20M
    post_max_size = 20M
    max_execution_time = 300
    max_input_time = 300
    
    [opcache]
    opcache.enable = 1
    opcache.memory_consumption = 256
    opcache.interned_strings_buffer = 16
    opcache.max_accelerated_files = 20000
    opcache.validate_timestamps = 0
    opcache.revalidate_freq = 0
    opcache.save_comments = 1
    opcache.fast_shutdown = 1
    
    [Session]
    session.save_handler = memcached
    session.save_path = "memcached:11211"
    session.gc_maxlifetime = 3600
    
    [Date]
    date.timezone = Europe/Paris
    Lenguaje del código: TOML, también INI (ini)
    [www]
    user = symfony
    group = symfony
    listen = 9000
    listen.owner = symfony
    listen.group = symfony
    
    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 10
    pm.min_spare_servers = 10
    pm.max_spare_servers = 20
    pm.max_requests = 500
    pm.status_path = /fpm-status
    ping.path = /fpm-ping
    request_terminate_timeout = 300
    request_slowlog_timeout = 10
    slowlog = /var/log/php-fpm/slow.log
    catch_workers_output = yes
    decorate_workers_output = no
    php_admin_value[error_log] = /var/log/php-fpm/error.log
    php_admin_flag[log_errors] = on
    Lenguaje del código: TOML, también INI (ini)
    framework:
        secret: '%env(APP_SECRET)%'
        session:
            handler_id: session.handler.memcached
            cookie_secure: auto
            cookie_samesite: lax
            save_path: '%env(MEMCACHED_HOST)%:%env(MEMCACHED_PORT)%'
            gc_maxlifetime: 3600
    
    services:
        session.handler.memcached:
            class: Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler
            arguments:
                - '@memcached.connection'
        
        memcached.connection:
            class: Memcached
            calls:
                - method: addServer
                  arguments:
                      - '%env(MEMCACHED_HOST)%'
                      - '%env(int:MEMCACHED_PORT)%'
                - method: setOption
                  arguments:
                      - !php/const Memcached::OPT_PREFIX_KEY
                      - 'symfony_'
                - method: setOption
                  arguments:
                      - !php/const Memcached::OPT_BINARY_PROTOCOL
                      - true
    Lenguaje del código: YAML (yaml)
    docker-compose build
    docker-compose up -d
    docker-compose ps
    
    docker-compose exec php-fpm-1 composer install
    docker-compose exec php-fpm-1 php bin/console doctrine:migrations:migrate --no-interaction
    Lenguaje del código: Bash (bash)

    Ejemplos

    Controlador de ejemplo para verificar persistencia de sesión y cuál contenedor atiende la petición.

    <?php
    
    namespace App\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    class TestController extends AbstractController
    {
        #[Route('/test/session', name: 'test_session')]
        public function testSession(Request $request): Response
        {
            $session = $request->getSession();
            
            $counter = $session->get('counter', 0);
            $counter++;
            $session->set('counter', $counter);
            
            $hostname = gethostname();
            
            return $this->json([
                'container' => $hostname,
                'session_id' => $session->getId(),
                'counter' => $counter,
                'timestamp' => time()
            ]);
        }
    }
    Lenguaje del código: PHP (php)

    Prueba desde la terminal usando cookies para mantener la sesión entre llamadas:

    curl -c cookies.txt -b cookies.txt http://localhost/test/session
    curl -c cookies.txt -b cookies.txt http://localhost/test/session
    curl -c cookies.txt -b cookies.txt http://localhost/test/session
    Lenguaje del código: Bash (bash)

    Checklist

    • Verificar que todos los PHP‑FPM apunten al mismo Memcached (host/puerto).
    • Asegurar permisos de lectura/escritura sobre sockets compartidos si se usan sockets Unix.
    • Configurar health checks (fpm-ping, nginx stub_status) y revisar logs.
    • Ajustar pm.max_children según RAM disponible y uso por worker.
    • Usar .env o secretos para credenciales y no versionarlas.

    Conclusión

    Con Nginx como balanceador, múltiples contenedores PHP‑FPM y Memcached para sesiones puedes escalar horizontalmente una aplicación Symfony sin cambios en el código de sesión.

    Comienza con tres contenedores PHP‑FPM, monitoriza CPU/memoria y ajusta pm.* o añade contenedores según la demanda. Implementa health checks y protege credenciales en producción.

  • 7 apps para Mac que mejoran productividad y flujo de trabajo

    7 apps para Mac que mejoran productividad y flujo de trabajo

    Resumen: este artículo presenta una selección de siete aplicaciones para macOS que optimizan la gestión de ventanas, atajos, descansos y captura de pantalla. Aquí encontrarás descripción, usos prácticos y un checklist para probarlas.

    Introducción

    Este texto resume características prácticas de siete aplicaciones para Mac que el autor descubrió y adoptó para mejorar su productividad diaria.

    El objetivo es ofrecer descripciones accionables para evaluar cada app y decidir si encaja en tu flujo de trabajo.

    Prerrequisitos

    Se asume que el lector usa macOS y dispone de permisos básicos para instalar y ejecutar aplicaciones. No se detallan versiones específicas del sistema operativo porque no están en la fuente original.

    Desarrollo (apps clave)

    DockDoor

    Problema que resuelve: macOS no muestra vistas previas de ventanas al pasar el cursor por el Dock o al cambiar entre ventanas de una misma app.

    Qué ofrece: añade vistas previas tipo Windows y un conmutador Alt/Tab mejorado (Opt + Tab por defecto). Permite configurar uso de Cmd + Tab si se prefiere.

    Personalización: se pueden ajustar ancho/alto de las vistas previas y su apariencia; la aplicación incluye soporte para efectos visuales como “Liquid Glass” según la descripción original.

    Rcmd

    Concepto: sustituye o complementa a Cmd + Tab usando la tecla Cmd derecha combinada con una letra para saltar directamente a una app.

    Ejemplo práctico: con Rcmd puedes presionar Right Cmd + S para ir directamente a Safari. Si hay varias apps que comparten la misma letra, se vuelve a pulsar la combinación para alternar entre ellas.

    LookAway

    Propósito: reducir la fatiga visual y mejorar la postura mediante recordatorios configurables de pausas y postura.

    Configuración descrita: alertas cada 30 min o 1 h; el autor usa 45 min. Duración de las pausas entre 20 y 60 segundos. También ofrece recordatorios de postura (el autor recomienda ~10 minutos).

    NotchNook

    Funcionalidad: aporta una “isla dinámica” al Mac (incluso si no tienes notch físico) para mostrar controles multimedia y accesos directos, además de una función de “mirror” que abre la webcam en una ventana emergente.

    Tray y acciones: incluye una “Tray” para arrastrar archivos y acceder rápidamente a ellos desde cualquier aplicación; permite activar AirDrop desde ese flujo según la descripción.

    Shareshot

    Para quién sirve: autores de contenido que toman muchas capturas y quieren presentarlas dentro de marcos (iPhone, Mac, Watch) sin ajustarlas manualmente.

    Comportamiento útil: la app elige automáticamente el marco según la relación de aspecto; la versión pro permite ajustar efectos de iluminación de fondo y eliminar la marca de agua.

    Launchy

    Qué hace: ofrece un lanzador radial que se invoca con un atajo (opt + cmd en el relato) y muestra las apps en una rueda para seleccionarlas visualmente.

    Personalización: tamaño del radial, mostrar/ocultar nombres, distancia entre iconos y múltiples conjuntos de apps se pueden configurar para ajustar el flujo de trabajo.

    Amphetamine

    Utilidad concreta: evita que el Mac entre en reposo en situaciones puntuales (por ejemplo, cuando se desconecta el cargador y el autor carga simultáneamente un iPhone con el mismo cable).

    Nota práctica: en el caso del autor fue útil desactivar la opción ‘Closed-Display Mode’ para mantener el equipo despierto en modo clamshell.

    Ejemplos (atajos y ajustes)

    Dos ejemplos prácticos extraídos de las descripciones para aplicar en pruebas rápidas.

    Right Command + S => Safari
    Presiona la misma combinación nuevamente para alternar entre apps que comparten la misma letra
    Lenguaje del código: texto plano (plaintext)

    Ejemplo de ajustes recomendados para recordatorios y pausas (LookAway):

    Intervalo de descanso recomendado: 45m
    Duración de descanso: 30s
    Recordatorio de postura: cada 10m
    Lenguaje del código: texto plano (plaintext)

    Checklist

    1. Instalar DockDoor y probar vistas previas al pasar el cursor por el Dock o usar Opt + Tab.
    2. Configurar Rcmd: asignar Right Cmd + <letra> para las apps más usadas y verificar alternancia si hay duplicados.
    3. Configurar LookAway con un intervalo que no interrumpa tu flujo (probar 45m y 30s por defecto).
    4. Probar NotchNook: comprobar controles multimedia, mirror y la Tray para compartir archivos rápidamente.
    5. Tomar capturas con Shareshot y verificar la selección automática de marcos; evaluar la versión pro si necesitas efectos y sin marca de agua.
    6. Probar Launchy: configurar conjuntos de apps y ajustar la rueda para lanzarlas con opt + cmd.
    7. Usar Amphetamine para mantener el equipo activo en escenarios de clamshell; revisar la opción ‘Closed-Display Mode’.

    Opcional: si usas muchos apps de pago, considera servicios tipo suscripción que agreguen catálogos (la fuente original menciona Setapp como opción de acceso a múltiples apps).

    Conclusión

    Estas siete aplicaciones ofrecen mejoras puntuales: gestión de ventanas, atajos rápidos, recordatorios de descanso, lanzadores visuales, y herramientas para capturas estéticamente presentadas.

    El autor concluye que descubrir herramientas pequeñas y bien pensadas puede ahorrar tiempo real y mejorar ergonomía. También menciona la opción de probar un servicio que da acceso a muchas apps por una cuota mensual y un código de afiliado (‘usefultech’).

    Próximo paso recomendado: seguir la checklist y evaluar cada app durante al menos una semana para medir el impacto real en tu flujo de trabajo.

  • Reducir el uso de Jira en equipos Agile: por qué y cómo

    Reducir el uso de Jira en equipos Agile: por qué y cómo

    Resumen: El uso intensivo de herramientas como Jira suele consumir gran parte del tiempo del equipo sin garantizar prácticas Agile reales. Este artículo explica por qué reducir ese uso y propone alternativas centradas en colaboración directa y automatización de pruebas.

    Introducción

    Durante años muchos equipos han puesto la herramienta de gestión (por ejemplo, Jira) en el centro del proceso. Eso convierte a la herramienta en el flujo de trabajo, no al equipo ni al software entregado.

    Using Jira does NOT mean doing Agile.

    El resultado observado: horas diarias perdidas en actualizar tickets, comentar requerimientos en la herramienta y usarla como registro de vida del proyecto, en vez de invertir ese tiempo en ejecutar, probar y entregar software.

    Prerrequisitos

    Antes de reducir la dependencia de una herramienta de gestión, verifique que el equipo cumpla algunas condiciones mínimas:

    1. Comunicación directa entre Product Owner, desarrolladores y testers.
    2. Cobertura razonable de pruebas end-to-end automatizadas que detecten y reproduzcan defectos.
    3. Canales simples para capturar decisiones y cambios (p. ej., notas mínimas o tarjetas físicas).

    Si alguna de esas condiciones falta, la reducción del uso de la herramienta debe ser gradual y acompañada de mejoras en comunicación y automatización.

    Desarrollo

    Procedimiento

    1. Identificar qué actividades realmente aportan valor cuando se hacen en la herramienta y cuáles son tareas administrativas repetitivas.
    2. Sustituir seguimientos y discusiones largas en la herramienta por reuniones cortas o comunicación directa y registro mínimo.
    3. Aumentar la inversión en pruebas end-to-end automatizadas que reproduzcan defectos y actúen como registro ejecutable.
    4. Preferir artefactos livianos (tarjetas físicas, notas de reunión) para planificación cuando facilite la conversación.
    5. Reducir a lo estrictamente necesario el tiempo en la herramienta: estados automáticos desde CI, notificaciones puntuales y un único punto de verdad para auditoría si es imprescindible.

    La idea no es eliminar el registro, sino desplazar el esfuerzo desde actualizar la herramienta hacia crear software y automatizar su verificación.

    Ejemplos

    Ejemplo práctico: cuando aparece un defecto, lo ideal es convertirlo en un caso de prueba reproducible y añadirlo a la suite de regresión automatizada en lugar de gestionarlo como un ticket administrativo prolongado.

    # Instalar dependencias
    npm ci
    # Ejecutar suite E2E (local o en CI)
    npm run test:e2e -- --grep "nombre_del_caso"
    # Si el caso reproduce el defecto, convertirlo en test y versionarlo
    git add tests/e2e/replicated-test.spec.js
    git commit -m "Añadir test reproducible para defecto XYZ"
    git push
    Lenguaje del código: Bash (bash)

    Ese flujo garantiza que el defecto quede registrado como prueba automatizada que previene regresiones futuras, y evita ciclos de vida largos en la herramienta de gestión.

    Checklist

    1. ¿El equipo escribe pruebas que reproduzcan defectos antes de crear tickets extensos?
    2. ¿Se usan reuniones cortas para aclarar requisitos en vez de hilos largos en la herramienta?
    3. ¿La CI ejecuta la suite de regresión y actualiza estados automáticamente cuando procede?
    4. ¿Hay un proceso mínimo para auditoría cuando el registro formal es necesario?

    Si la respuesta a la mayoría de estos items es sí, el equipo está listo para minimizar su dependencia de una herramienta de gestión.

    Conclusión

    Poner la herramienta en el centro del proceso no equivale a hacer Agile. Reducir el tiempo que el equipo pasa en herramientas como Jira libera recursos para construir, probar y entregar software con más frecuencia. La vía práctica es reforzar comunicación directa, usar artefactos ligeros y priorizar la automatización que capture y prevenga defectos.

  • Variables, tipos y arrays en PHP: conceptos esenciales

    Variables, tipos y arrays en PHP: conceptos esenciales

    Breve guía práctica sobre variables, tipos de datos y arrays en PHP. Incluye ejemplos de sintaxis, comprobación de tipos y patrones comunes para manipular arrays.

    Apunta a desarrolladores que ya conocen lo básico de PHP y quieren reforzar conceptos clave para construir aplicaciones dinámicas.

    Introducción

    Variables, tipos de datos y arrays son la base de cualquier programa en PHP: almacenan y organizan la información que tu aplicación procesa. Aquí verás reglas sintácticas, herramientas de depuración y patrones prácticos para trabajar con datos.

    Prerrequisitos

    Se asume que dispones de un entorno con PHP instalado y acceso para ejecutar scripts (CLI o servidor). Si no, configura un entorno local antes de probar los ejemplos.

    Desarrollo

    Variables y sintaxis

    En PHP las variables comienzan con $ y distinguen mayúsculas de minúsculas. Deben iniciar con una letra o guion bajo. Ejemplo básico:

    <?php
    $name = "Nikul";
    $age = 25;
    
    echo "Hello, my name is $name and I am $age years old.";
    ?>
    Lenguaje del código: PHP (php)

    Tipos de datos y comprobación

    PHP es de tipado dinámico: el tipo se determina en tiempo de ejecución. Para depurar y comprobar tipos se usan funciones como var_dump() y gettype().

    <?php
    $name = "Nikul";
    $age = 25;
    
    var_dump($name); // muestra tipo y valor
    echo gettype($age); // muestra "integer"
    ?>
    Lenguaje del código: PHP (php)

    Operadores y conversión

    Usa operadores aritméticos, lógicos y de comparación según necesites. Para forzar tipos existen casts explícitos (por ejemplo (int), (string)).

    Ejemplos

    Arrays indexados

    Los arrays indexados almacenan valores accesibles por índice numérico. Ejemplo y acceso al primer elemento:

    <?php
    $colors = ["red", "green", "blue"];
    echo $colors[0]; // red
    ?>
    Lenguaje del código: PHP (php)

    Arrays asociativos

    Los arrays asociativos usan claves legibles para modelar objetos simples (por ejemplo, un usuario). Accede por la clave:

    <?php
    $user = [
      "name" => "Nikul",
      "age" => 25,
      "email" => "nikul@example.com"
    ];
    
    echo $user["email"]; // nikul@example.com
    ?>
    Lenguaje del código: PHP (php)

    Arrays multidimensionales

    Para estructuras tabulares o listas de objetos, usa arrays multidimensionales y accede por índices anidados:

    <?php
    $students = [
      ["name" => "Amit", "score" => 90],
      ["name" => "Sara", "score" => 85]
    ];
    
    echo $students[1]["name"]; // Sara
    ?>
    Lenguaje del código: PHP (php)

    Recorrer arrays (foreach)

    El bucle foreach es el patrón más común para iterar arrays indexados y asociativos:

    <?php
    $colors = ["red", "green", "blue"];
    foreach ($colors as $color) {
      echo $color . "\n";
    }
    
    $user = ["name" => "Nikul", "age" => 25];
    foreach ($user as $key => $value) {
      echo "$key: $value\n";
    }
    ?>
    Lenguaje del código: PHP (php)

    Checklist

    1. Usar nombres de variable claros y consistentes
    2. Verificar tipos con var_dump() o gettype() durante la depuración
    3. Preferir arrays asociativos para datos con claves significativas
    4. Evitar mezclar tipos dentro de un mismo array cuando sea crítico
    5. Usar foreach para iteraciones legibles y eficientes

    Conclusión

    Dominar variables, tipos y arrays en PHP te permitirá modelar datos correctamente y construir lógica más robusta. Practica los ejemplos y aplica la checklist en tus proyectos para evitar errores comunes.

    Próximo paso sugerido: revisar control de flujo (if, switch, loops) para combinar estructuras de datos con lógica de aplicación.

  • Agentes AI en PHP con MCP y Neuron

    Agentes AI en PHP con MCP y Neuron

    Resumen: Este artículo explica cómo integrar agentes AI escritos en PHP con servidores MCP usando el framework Neuron. Incluye requisitos, flujo de trabajo y ejemplos de código.

    Se cubre la arquitectura mínima (Host, MCP server, MCP client), cómo cargar herramientas desde un servidor MCP y ejemplos prácticos en PHP.

    Introducción

    MCP (Model Context Protocol) normaliza la exposición de APIs y herramientas para que los LLM puedan descubrir y ejecutar funciones. En lugar de implementar cada integración desde cero, un servidor MCP actúa como capa intermedia que traduce y presenta herramientas listas para LLM.

    Los modelos de lenguaje por sí solos gestionan texto; necesitan herramientas para ejecutar acciones en el mundo real.

    Prerrequisitos

    Antes de empezar necesita PHP (versión compatible con su proyecto), Composer y acceso al repositorio de Neuron. También, durante desarrollo, deberá instalar el MCP server localmente.

    composer require inspector-apm/neuron-aiLenguaje del código: Bash (bash)

    Desarrollo

    En términos prácticos MCP requiere tres componentes: un Host donde corre el agente, un MCP server que expone herramientas y un MCP client que permite al agente comunicarse con el servidor (normalmente vía stdio en implementaciones locales).

    Procedimiento

    • Instalar el MCP server en la misma máquina del agente (desarrollo y producción cuando aplique).
    • Configurar el servidor MCP y exponer las herramientas necesarias.
    • Usar Neuron y su McpConnector para descubrir y cargar herramientas en el agente.

    Neuron integra la conexión con MCP para que, cuando el agente decida ejecutar una herramienta, la petición se envíe al MCP server y el resultado vuelva al LLM como si fuera una herramienta nativa.

    Ejemplos

    Ejemplo mínimo: un agente PHP que define un proveedor de IA y sus instrucciones. Este ejemplo usa la estructura de Neuron para declarar el proveedor.

    <?php
    use NeuronAI\Agent;
    use NeuronAI\Providers\AIProviderInterface;
    use NeuronAI\Providers\Anthropic\Anthropic;
    
    class MyAgent extends Agent
    {
        public function provider(): AIProviderInterface
        {
            return new Anthropic(
                key: 'ANTHROPIC_API_KEY',
                model: 'ANTHROPIC_MODEL',
            );
        }
    
        public function instructions(): string
        {
            return "LLM system instructions.";
        }
    }
    Lenguaje del código: PHP (php)

    Agregar herramientas y conectar un MCP server con McpConnector para cargar las herramientas expuestas automáticamente. En este ejemplo se combinan las herramientas recuperadas desde un servidor MCP con herramientas personalizadas.

    <?php
    use NeuronAI\Agent;
    use NeuronAI\Providers\AIProviderInterface;
    use NeuronAI\Providers\Anthropic\Anthropic;
    use NeuronAI\Tools\Tool;
    use NeuronAI\Tools\ToolProperty;
    
    class MyAgent extends Agent
    {
        public function provider(): AIProviderInterface
        {
            return new Anthropic(
                key: 'ANTHROPIC_API_KEY',
                model: 'ANTHROPIC_MODEL',
            );
        }
    
        public function instructions(): string
        {
            return "LLM system instructions.";
        }
    
        public function tools(): array
        {
            return [
               // Cargar herramientas desde un MCP server
               ...McpConnector::make([
                    'command' => 'npx',
                    'args' => ['-y', '@modelcontextprotocol/server-everything'],
                ])->tools(),
      
                // Herramienta personalizada
                Tool::make(
                    "get_article_content", 
                    "Use the ID of the article to get its content."
                )->addProperty(
                    new ToolProperty(
                        name: 'article_id',
                        type: 'integer',
                        description: 'The ID  of the article you want to analyze.',
                        required: true
                    )
                )->setCallable(function (string $article_id) {
                    // Use su capa de datos aquí
                    global $pdo;
                    $stmt = $pdo->prepare("SELECT * FROM articles WHERE id=? LIMIT 1");
                    $stmt->execute([$article_id]);
                    return json_encode(
                        $stmt->fetch(PDO::FETCH_ASSOC)
                    );
                })
            ];
        }
    }
    Lenguaje del código: PHP (php)

    Puede encontrar más ejemplos y una sección de instalación en la documentación oficial: https://docs.neuron-ai.dev/installation

    Checklist

    1. Instalar Neuron via Composer en el proyecto.
    2. Instalar y configurar el MCP server localmente (desarrollo).
    3. Usar McpConnector para descubrir y cargar herramientas automáticamente.
    4. Definir herramientas personalizadas que usen las APIs internas (DB, servicios externos).
    5. Probar el flujo: agente decide, ejecuta herramienta en MCP, recibe resultado y continúa la resolución.

    Conclusión

    MCP facilita exponer herramientas listas para LLM y reduce la complejidad de integrar múltiples servicios. Con Neuron en PHP puede aprovechar MCP para crear agentes capaces de ejecutar acciones complejas sin replicar integraciones desde cero.

    Empiece instalando Neuron, ponga en marcha un MCP server local y use McpConnector para acelerar la integración de herramientas en sus agentes.

  • Laravel HelperBox: 600+ helpers listos para producción

    Laravel HelperBox: 600+ helpers listos para producción

    Laravel HelperBox reúne 600+ helpers listos para producción que reducen boilerplate, detectan N+1, agregan caching con jitter y simplifican clientes HTTP.

    Introducción

    Muchas aplicaciones Laravel invierten tiempo en funciones utilitarias repetitivas. HelperBox consolida 600+ helpers probados en producción para acelerar el desarrollo y reducir errores comunes.

    Prerrequisitos

    Compatibilidad mencionada: Laravel 9–12 y PHP 8.0+. La instalación es rápida vía Composer y la librería se auto-descubre.

    composer require subhashladumor/laravel-helperbox
    php artisan tinker  # Test: array_flatten_recursive()Lenguaje del código: Bash (bash)

    Desarrollo

    En esta sección se describen los pasos recomendados para integrar y validar los helpers en un proyecto existente.

    Procedimiento

    1. Instalar el paquete con Composer.
    2. Probar funciones críticas en Tinker o tests unitarios.
    3. Validar detección de N+1, configuraciones de cache y clientes HTTP en entornos de staging.
    4. Desplegar con monitoreo y rollback rápido si aparece un problema.

    Ejemplos

    Fragmentos de uso reales para los helpers más relevantes. Copia y adapta según tu contexto.

    <?php
    // Detectar N+1 en una consulta Eloquent
    $issues = db_detect_n_plus_one(
        Order::query(),
        ['customer', 'items', 'payments']
    );
    
    // Ejemplo de salida simplificada
    [
        'n_plus_one_detected' => true,
        'relation' => 'items',
        'estimated_queries' => 1500,
        'fix_suggestion' => "Use with('items')"
    ]Lenguaje del código: PHP (php)
    <?php
    // Caching con jitter para evitar stampedes
    $leaderboard = cache_with_jitter(
        'global:leaderboard',
        900,   // TTL: 15min
        60,    // Jitter: ±60s
        fn() => User::leaderboard()
    );Lenguaje del código: PHP (php)
    <?php
    // Clientes HTTP de producción (JSON)
    $repos = http_get_json('https://api.github.com/users/laravel/repos');
    
    // Subir archivos sin boilerplate
    http_post_formdata($uploadUrl, [
        'file' => $request->file('avatar'),
        'user_id' => $user->id,
    ]);Lenguaje del código: PHP (php)
    <?php
    // Ejemplos rápidos en Tinker
    array_flatten_recursive([[1,[2,[3,4]]],5]);
    // >> [1, 2, 3, 4, 5]
    
    str_slugify('  Café with Résumé?  ');
    // >> "cafe-with-resume"Lenguaje del código: PHP (php)

    Checklist

    • Confirmar instalación y auto-discovery del ServiceProvider.
    • Ejecutar tests unitarios que cubran los helpers usados.
    • Validar detección de N+1 y aplicar with() cuando sea necesario.
    • Configurar TTL y jitter en caches críticos.
    • Revisar licencia MIT y compatibilidad del proyecto.

    Conclusión

    Laravel HelperBox ofrece una colección práctica de helpers que pueden reducir horas de trabajo repetitivo y mitigar problemas comunes como N+1 o stampedes de cache. Integrarlo con pruebas y validación en staging maximiza su beneficio.

  • Ingeniería del LEGO Soundwave: análisis del mecanismo de transformación

    Ingeniería del LEGO Soundwave: análisis del mecanismo de transformación

    Resumen: este artículo ofrece un análisis de ingeniería del set LEGO Soundwave, centrándose en sus mecanismos de transformación, compromisos de diseño y recomendaciones prácticas para montaje y exhibición.

    Se describen la arquitectura torso-primaria, el brick sonoro integrado, los ensambles Technic y una checklist técnica para evaluar robustez y facilidad de transformación.

    Introducción

    El set LEGO Soundwave integra piezas tradicionales y un brick sonoro para habilitar una transformación entre robot y microcassette. Aquí se examinan sus decisiones de ingeniería, limitaciones y consecuencias para robustez y mantenimiento.

    Prerrequisitos

    Antes de abordar el análisis técnico, ten en cuenta la información pública del set que sirve como base:

    • Recuento de piezas: 1505.
    • Precio de venta informado: 180 USD.
    • Manual: 236 páginas, ~370 pasos de montaje distribuidos en 12 etapas.
    • Brick sonoro con 39 efectos (valores según documentación del set).

    Si necesitas replicar pruebas o diagnósticos: ten a mano espacio de trabajo, etiquetado de piezas y paciencia para procedimientos repetitivos en las extremidades.

    Desarrollo

    Este apartado detalla la arquitectura de construcción, los mecanismos críticos y los trade-offs detectados durante el montaje.

    Procedimiento

    Arquitectura torso-primaria: el conjunto se construye mayoritariamente desde el torso hacia las extremidades para centralizar la instalación del brick sonoro y servir de armazón para las piezas móviles.

    Brick sonoro y compartimento de casete: el brick se integra en la etapa cinco del manual y se activa mediante un botón en la parte frontal del torso. El mecanismo de la puerta del casete usa una solución mecánica confiable y un elemento impreso transparente con logotipo.

    Piezas transformables: las dos microcassettes son también sub-modelos transformables (Laserbeak y Ravage). Se entregan con baldosas impresas exclusivas.

    Articulaciones y ensamblajes Technic: brazos y piernas emplean ensamblajes Technic y articulaciones tipo ball-joint. Los ball-joints proporcionan buena retención inicial, pero pueden perder agarre por tensión prolongada del plástico.

    Compromisos de diseño observados:

    • Robustez vs. transformabilidad: la libertad de movimiento añade puntos débiles estructurales que el diseñador compensa con repetición de piezas y ensamblajes internos.
    • Repetitividad y fatiga del montador: las piernas son las secciones más complejas y repetitivas del montaje.
    • Mantenimiento: piezas no exclusivas permiten reemplazos económicos cuando ball-joints pierden su fricción.

    Transformación: el set completa la transformación en 12 pasos, comparado con 18 para Optimus Prime y 24 para Bumblebee según la documentación del fabricante.

    Accesorios y acabado: la cabeza se monta al final y ofrece opción de color en las piezas de ojos; los pies incorporan neumáticos horizontales para mejorar la adherencia en superficies de exposición.

    Ejemplos

    Ejemplo 1: representación simple de la máquina de estados para la transformación en TypeScript.

    const steps = [
      'start',
      'build_torso',
      'build_cassettes',
      'integrate_soundbrick',
      'assemble_arms',
      'assemble_legs',
      'attach_head',
      'finalize_connections',
      'attach_accessories',
      'robot_pose_check',
      'transform_step_1_to_11',
      'transform_step_12_complete'
    ];
    
    type Step = typeof steps[number];
    
    function nextStep(current: Step): Step | null {
      const i = steps.indexOf(current);
      return i >= 0 && i < steps.length - 1 ? steps[i + 1] as Step : null;
    }
    
    // Uso
    let s: Step = 'build_torso';
    while (s) {
      console.log('Ejecutando:', s);
      s = nextStep(s) as Step | null;
    }
    Lenguaje del código: TypeScript (typescript)

    Ejemplo 2: manifiesto JSON con metadatos del set (para catalogación o importación en herramientas de inventario).

    {
      "pieces": 1505,
      "price_usd": 180,
      "manual_pages": 236,
      "approx_steps": 370,
      "stages": 12,
      "sound_effects": 39,
      "transform_steps": 12,
      "comparison": { "optimus_prime": 18, "bumblebee": 24 }
    }
    

    Checklist

    1. Verificar integridad del torso y fijación del brick sonoro.
    2. Confirmar funcionamiento del mecanismo de la puerta del casete y del botón sonoro.
    3. Probar fricción de ball-joints en brazos y cabeza; plan de reemplazo si es necesario.
    4. Revisar repetitividad en ensamblajes de piernas para detectar errores de montaje.
    5. Evaluar estabilidad en modo display (robot) y en modo Walkman; comprobar adherencia de los neumáticos de los pies.
    6. Registrar y almacenar piezas impresas o exclusivas (p. ej. tiles impresos) para preservación o venta futura.

    Consejo: documenta las piezas no exclusivas que puedan fallar y ten repuestos para preservar la capacidad de pose y transformación.

    Conclusión

    El diseño del LEGO Soundwave equilibra la experiencia de transformación con limitaciones estructurales inherentes a las piezas móviles. La inclusión del brick sonoro añade valor funcional y condiciona la arquitectura torso-primaria.

    Para arquitectos y desarrolladores de hardware/mezcla mecánica-electrónica, el set es un caso práctico de trade-offs: modularidad, redundancia de piezas y facilidad de mantenimiento frente a libertad cinemática.

    Aplica la checklist y los ejemplos de máquina de estados para documentar procesos de montaje, pruebas y conservación del modelo en colecciones o exhibiciones.

  • Sitio personal mínimo con PHP: router y caché sin dependencias

    Sitio personal mínimo con PHP: router y caché sin dependencias

    Esta guía describe un enfoque minimalista para un sitio personal usando PHP puro: router simple, caché con sqlite y mínimos componentes externos para reducir mantenimiento.

    Incluye ejemplos de código para el router, la regla de Apache (.htaccess), el script de cacheo y la tabla sqlite para reproducir el patrón.

    Introducción

    Para un sitio personal mayoritariamente estático, minimizar dependencias reduce complejidad operativa: menos builds, menos procesos a gestionar y actualizaciones centralizadas por el gestor de paquetes del sistema.

    Prerrequisitos

    Basado en la descripción original, los requisitos esenciales son:

    • Servidor HTTP (Apache) con soporte para rewrite y uso de index.php como front controller.
    • PHP instalado en el sistema y extensión pdo_sqlite para acceso a sqlite.
    • Acceso para programar tareas (cron) si se usa un script que actualice la caché.
    • Permisos de filesystem para que PHP lea/usen el directorio pages/ y escriba la base sqlite.

    Desarrollo

    La idea central es un front controller (index.php) que resuelve rutas estáticas buscando ficheros en pages/<path>/index.php o pages/<path>.php y que carga meta datos desde un array PHP.

    Procedimiento

    Flujo básico del router:

    1. Normalizar REQUEST_URI y eliminar query string.
    2. Componer rutas candidatas: pages/<path>/index.php y pages/<path>.php.
    3. Incluir el primer fichero existente dentro del layout principal o enviar 404 si no existe.

    Ejemplo de router sencillo (54 líneas en la implementación original):

    <?php
    // Simple front controller: index.php
    $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    $path = trim($uri, '/');
    if ($path === '') {
        $path = 'index';
    }
    
    $candidates = [
        __DIR__ . '/pages/' . $path . '/index.php',
        __DIR__ . '/pages/' . $path . '.php',
    ];
    
    $found = null;
    foreach ($candidates as $file) {
        if (file_exists($file)) {
            $found = $file;
            break;
        }
    }
    
    // Metadata map: ruta => título (ejemplo mínimo)
    $meta = [
        'index' => 'Inicio',
        'music' => 'Música',
    ];
    
    if ($found) {
        $title = $meta[$path] ?? 'Página';
        // variables disponibles para el layout
        $contentFile = $found;
        include __DIR__ . '/templates/layout.php';
        exit;
    }
    
    http_response_code(404);
    include __DIR__ . '/pages/404.php';
    Lenguaje del código: PHP (php)

    El layout (templates/layout.php) simplemente incluye el fichero de contenido dentro de la estructura HTML del sitio y usa $title para el <title>.

    Ejemplos

    A continuación se muestran fragmentos prácticos: la regla de Apache para ocultar index.php, la tabla sqlite, y un script cron que actualiza la caché.

    Regla mínima en .htaccess para enviar todas las solicitudes a index.php salvo ficheros existentes:

    RewriteEngine On
    # Si el fichero o directorio existe, servirlo directamente
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule .* - [L]
    # En caso contrario, reenviar a index.php
    RewriteRule ^ index.php [L,QSA]
    Lenguaje del código: texto plano (plaintext)

    Ejemplo de esquema sqlite para almacenar elementos cacheados (tabla simple):

    CREATE TABLE IF NOT EXISTS blog_cache (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      source TEXT NOT NULL,
      title TEXT NOT NULL,
      excerpt TEXT,
      featured_image TEXT,
      url TEXT NOT NULL,
      fetched_at INTEGER NOT NULL
    );
    Lenguaje del código: SQL (Structured Query Language) (sql)

    Script de ejemplo (cron) que ejecuta un script PHP para actualizar la caché desde fuentes externas:

    # Entrar en /var/www/site y ejecutar el actor que actualiza sqlite
    cd /var/www/site || exit 1
    /usr/bin/php scripts/update_blog_cache.php
    Lenguaje del código: Bash (bash)

    Fragmento PHP que actualiza sqlite usando PDO: (ejemplo mínimo, adaptar parsing de cada fuente)

    <?php
    $db = new PDO('sqlite:' . __DIR__ . '/../data/blog_cache.sqlite3');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    // Ejemplo: array de fuentes (URLs) a procesar
    $sources = [
        'https://example.com/feed',
    ];
    
    $stmt = $db->prepare('INSERT INTO blog_cache (source,title,excerpt,featured_image,url,fetched_at) VALUES (:source,:title,:excerpt,:image,:url,:ts)');
    
    foreach ($sources as $src) {
        // Simplificado: obtener HTML/JSON y extraer títulos; aquí se usa file_get_contents como ejemplo
        $body = @file_get_contents($src);
        if ($body === false) {
            continue;
        }
    
        // Extraer ítems: esto depende del formato de la fuente. Aquí se asume extracción manual.
        // Insertar un ejemplo ficticio de fila (reemplazar por parsing real)
        $stmt->execute([
            ':source' => $src,
            ':title' => 'Título ejemplo',
            ':excerpt' => 'Extracto ejemplo',
            ':image' => '',
            ':url' => $src,
            ':ts' => time(),
        ]);
    }
    Lenguaje del código: PHP (php)

    En la página que muestra la lista de blogs, lee la tabla blog_cache para renderizar los últimos registros en lugar de pedir cada feed en tiempo real.

    Checklist

    1. Instalar PHP y habilitar pdo_sqlite desde el gestor de paquetes del sistema.
    2. Configurar Apache y añadir la regla .htaccess para el front controller.
    3. Crear estructura de carpetas: pages/, templates/, data/ y scripts/.
    4. Implementar el script cron para actualizar la caché sqlite y verificar permisos de escritura.
    5. Probar rutas estáticas y 404, revisar logs de Apache y PHP para errores.

    Conclusión

    Un sitio personal ligero implementado con PHP puro permite mantener control total sobre la superficie de mantenimiento al costo de escribir piezas pequeñas como el router y scripts de cacheo.

    Esta aproximación favorece simplicidad operativa: menos dependencias, actualizaciones gestionadas por el sistema y procesos reducidos. Para sitios personales mayoritariamente estáticos, la diferencia frente a soluciones estáticas o frameworks pesados suele ser negligente en rendimiento y sustancial en simplicidad.

    Si quieres, puedo adaptar los ejemplos para una estructura concreta de páginas, o convertir el router en una versión con logging y manejo básico de errores.