# Angel Cruz · Software Developer — full corpus > Index completo con el cuerpo de cada artículo embebido. Versión exhaustiva de llms.txt pensada para agentes que ingieren el sitio entero en una sola lectura. Para el índice corto (sólo links + descripciones), ver https://www.angelcruz.dev/llms.txt. Cada post se sirve también individualmente con `Accept: text/markdown` o el sufijo `.md` en su URL. ## Páginas principales - [Home](https://www.angelcruz.dev/) - [Acerca de mí](https://www.angelcruz.dev/acerca-de-mi) - [Contacto](https://www.angelcruz.dev/contacto) - [Blog](https://www.angelcruz.dev/post) - [Categorías](https://www.angelcruz.dev/categorias) - [Lab](https://www.angelcruz.dev/lab) - [Open Source](https://www.angelcruz.dev/open-source) - [Uses](https://www.angelcruz.dev/uses) - [Tools](https://www.angelcruz.dev/tools) - [Servicios](https://www.angelcruz.dev/servicios) ## Artículos del blog (cuerpo completo) --- ### == vs === en PHP: igualdad suelta vs estricta - URL: https://www.angelcruz.dev/post/igual-vs-identico-php - Markdown: https://www.angelcruz.dev/post/igual-vs-identico-php.md - Categoría: PHP - Fecha: 2026-06-16 - Excerpt: La diferencia entre == y === en PHP explicada con ejemplos: conversión de tipos, el cambio de PHP 8, los gotchas de switch e in_array, y cómo comparar arrays y objetos. --- title: "== vs === en PHP: igualdad suelta vs estricta" excerpt: "La diferencia entre == y === en PHP explicada con ejemplos: conversión de tipos, el cambio de PHP 8, los gotchas de switch e in_array, y cómo comparar arrays y objetos." date: "2026-06-16T00:00:00.000Z" lastModified: "2026-06-16T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "== vs === en PHP: diferencia entre igual e idéntico" seo_description: "Diferencia entre == y === en PHP con ejemplos: == compara valor (con conversión de tipos), === compara valor y tipo. El cambio de PHP 8 y los gotchas de switch e in_array." tech_article: article_section: "PHP Fundamentals" keywords: "== vs === php, diferencia == y === php, igualdad estricta php, comparacion php, operador identico php" is_part_of: url: "https://www.angelcruz.dev/post/operadores-php" --- En PHP, `==` compara **solo el valor** (aplicando conversión de tipos), mientras que `===` compara **valor y tipo**. Por eso `5 == "5"` es `true`, pero `5 === "5"` es `false`. La regla práctica: usa `===` por defecto. Evita los bugs sutiles que provoca la conversión automática de tipos. Este artículo forma parte de la [guía de operadores en PHP](/post/operadores-php). Aquí me enfoco en la comparación que más confunde. ## `==` : igualdad con conversión de tipos El operador `==` (igual) devuelve `true` cuando los dos operandos tienen el **mismo valor**, incluso si son de tipos distintos. Antes de comparar, PHP convierte uno de los operandos para que ambos sean del mismo tipo. A esto se le llama *type juggling*: ```php var_dump(5 == "5"); // true — la string "5" se trata como el número 5 var_dump(true == 1); // true — true equivale a 1 var_dump(null == false); // true — ambos son "vacíos" var_dump(0 == false); // true ``` Es cómodo, pero peligroso: ese ajuste automático esconde comparaciones que no son las que esperas. ## `===` : identidad (valor y tipo) El operador `===` (idéntico) devuelve `true` solo si los operandos tienen el **mismo valor y el mismo tipo**. No hay conversión de por medio: ```php var_dump(5 === "5"); // false — int vs string var_dump(true === 1); // false — bool vs int var_dump(null === false); // false — null vs bool var_dump(5 === 5); // true — mismo valor, mismo tipo ``` Es la comparación segura y predecible. Lo que ves es lo que se compara. ## Tabla de comparación | Expresión | `==` | `===` | |---|---|---| | `5` y `"5"` | `true` | `false` | | `0` y `false` | `true` | `false` | | `null` y `false` | `true` | `false` | | `"" ` y `null` | `true` | `false` | | `"1"` y `"01"` | `true` | `false` | | `"10"` y `"1e1"` | `true` | `false` | | `0` y `"foo"` (PHP 8) | `false` | `false` | | `5` y `5` | `true` | `true` | Fíjate en `"1" == "01"` y `"10" == "1e1"`: cuando **ambos** operandos son strings numéricas, PHP las compara como números. Por eso `"01"` y `"1e1"` (notación científica de 10) entran en juego. Con `===` nunca pasa: distinto string, distinto resultado. ## El cambio de PHP 8 que rompe código viejo Hasta PHP 7, comparar un número con una string **no numérica** convertía la string a `0`. Eso hacía que `0 == "foo"` fuera `true`, una de las fuentes de bugs más clásicas del lenguaje. PHP 8 lo cambió: ahora convierte el número a string: ```php // PHP 7: true | PHP 8: false var_dump(0 == "foo"); // PHP 7: true | PHP 8: false var_dump(0 == ""); ``` Si migras un proyecto de PHP 7 a 8, este es uno de los cambios de comportamiento que más vale revisar. Y es, otra vez, un argumento a favor de usar `===`. ## `!=` vs `!==` (las versiones negadas) La misma lógica aplica a la desigualdad: - `!=` (o su alias `<>`) es la negación de `==`: `true` si los valores difieren tras la conversión de tipos. - `!==` es la negación de `===`: `true` si difieren en valor **o** en tipo. ```php var_dump(5 != "5"); // false — para != son iguales (mismo valor) var_dump(5 !== "5"); // true — para !== difieren (distinto tipo) ``` ## Dónde PHP usa `==` sin que lo veas El gran problema de `==` no es cuando lo escribes a propósito, sino cuando PHP lo usa por ti: **`switch` usa comparación suelta.** Esto era una trampa en PHP 7: ```php $valor = 0; switch ($valor) { case "admin": // PHP 7: 0 == "admin" era true → ¡entraba aquí! echo "es admin"; break; default: echo "otro valor"; } ``` En PHP 7 ese `switch` caía en `"admin"` por la conversión de tipos. Para evitarlo, usa `match` (PHP 8+), que compara con `===`: ```php $resultado = match ($valor) { "admin" => "es admin", default => "otro valor", // 0 nunca coincide con "admin" }; ``` **`in_array()` y `array_search()` comparan con `==` por defecto.** Pásales `true` como tercer argumento para forzar comparación estricta: ```php $roles = ['admin', 'editor']; var_dump(in_array(0, $roles)); // PHP 7: true (¡bug!) | PHP 8: false var_dump(in_array(0, $roles, true)); // false siempre — usa === ``` Acostúmbrate a pasar el `true`: es la versión segura. ## Comparar arrays y objetos Con tipos compuestos, la diferencia entre `==` y `===` tiene matices propios. **Arrays:** ```php $a = ['x' => 1, 'y' => 2]; $b = ['y' => 2, 'x' => 1]; // mismas claves/valores, distinto orden var_dump($a == $b); // true — mismos pares clave/valor (el orden no importa) var_dump($a === $b); // false — el orden de las claves difiere ``` Con `===`, dos arrays son idénticos solo si tienen los **mismos pares clave/valor, en el mismo orden y del mismo tipo**. **Objetos:** ```php class Punto { public function __construct(public int $x, public int $y) {} } $p1 = new Punto(1, 2); $p2 = new Punto(1, 2); $p3 = $p1; var_dump($p1 == $p2); // true — misma clase y mismos atributos var_dump($p1 === $p2); // false — son dos instancias distintas var_dump($p1 === $p3); // true — $p3 es la misma instancia que $p1 ``` Para objetos, `==` compara clase y atributos; `===` exige que sean **la misma instancia** en memoria. ## Cuándo usar cada uno - **`===` por defecto.** Es predecible, evita los bugs de conversión y deja la intención clara. En la práctica, es lo que quieres el 95% de las veces. - **`==` solo cuando la conversión de tipos es intencional** y la entiendes bien (por ejemplo, comparar un valor de formulario que llega como string contra un número). Aun así, casi siempre es más limpio convertir el tipo de forma explícita y usar `===`. ## Preguntas frecuentes ### ¿Cuál es la diferencia entre `==` y `===` en PHP? `==` compara solo el valor, convirtiendo los tipos si hace falta (`5 == "5"` es `true`). `===` compara valor **y** tipo, sin conversión (`5 === "5"` es `false`). Usa `===` por defecto para evitar bugs de conversión de tipos. ### ¿Por qué `0 == "texto"` daba `true` en PHP 7? Porque PHP 7 convertía la string no numérica a `0` antes de comparar, así que quedaba `0 == 0`. PHP 8 cambió ese comportamiento: ahora convierte el número a string, y `0 == "texto"` es `false`. ### ¿Es seguro usar `in_array()`? Por defecto no, porque compara con `==`. Pasa `true` como tercer argumento (`in_array($valor, $array, true)`) para que use comparación estricta y evites coincidencias inesperadas. ### ¿`===` es más rápido que `==`? Marginalmente, porque se salta la conversión de tipos, pero la diferencia es insignificante. Elige el operador por corrección, no por velocidad: `===` gana por ser predecible, no por rendimiento. ## Cierre La diferencia entre `==` y `===` se reduce a una sola idea: `===` también compara el tipo, `==` no. Como `==` puede convertir tipos por detrás —y PHP lo usa en `switch`, `in_array()` y más—, lo seguro es usar `===` por defecto y reservar `==` para los pocos casos donde la conversión es justo lo que buscas. Para repasar el resto de comparadores y operadores del lenguaje, vuelve a la [guía de operadores en PHP](/post/operadores-php). --- ### Cómo instalar OpenClaw con Docker Compose - URL: https://www.angelcruz.dev/post/instalar-openclaw-con-docker-compose - Markdown: https://www.angelcruz.dev/post/instalar-openclaw-con-docker-compose.md - Categoría: OpenClaw - Fecha: 2026-06-16 - Excerpt: Instalación de OpenClaw con Docker y Docker Compose paso a paso: imagen oficial en GitHub Container Registry, el script de setup, dónde se persiste la configuración y cómo actualizar sin perder datos. --- title: "Cómo instalar OpenClaw con Docker Compose" excerpt: "Instalación de OpenClaw con Docker y Docker Compose paso a paso: imagen oficial en GitHub Container Registry, el script de setup, dónde se persiste la configuración y cómo actualizar sin perder datos." date: "2026-06-16" lastModified: "2026-06-16" category: "OpenClaw" tech_article: true seo_title: "Instalar OpenClaw con Docker y Docker Compose (2026)" seo_description: "Guía oficial para correr OpenClaw en Docker Compose: imagen ghcr.io/openclaw/openclaw, el script de setup, volúmenes que persisten tu config y cómo actualizar con docker compose pull." author: name: "Angel Cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- Docker es la forma más limpia de correr [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source) cuando quieres aislarlo del resto del sistema: el agente vive en su propio contenedor, las actualizaciones son un comando, y el entorno es idéntico en cualquier máquina. La propia documentación lo describe como una opción pensada para **entornos aislados** o para validar el flujo antes de instalarlo nativo. Esta es la vía Docker; si buscas el panorama completo (script, npm, plataformas), está en la [guía de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## ¿Docker o instalación nativa? | | Docker Compose | Script / npm | | --- | --- | --- | | Aislamiento | ✅ contenedor propio | ❌ corre sobre tu sistema | | Actualizar | `docker compose pull` | reinstalar el paquete | | Peso | Mayor (capa de Docker) | Menor (Node nativo) | | Reproducible | ✅ idéntico en todos lados | depende del host | Si ya trabajas con contenedores o quieres mantener OpenClaw separado del resto, Docker. Si buscas lo más liviano —por ejemplo en una [Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi)— el script con Node nativo suele convenir más. ## Requisitos - **Docker** y **Docker Compose v2** instalados - Una **API key** de tu proveedor de IA (Claude, OpenAI o un modelo local) - Al menos **4 GB de RAM** disponibles para el contenedor ## Instalación con el script oficial El soporte de Docker de OpenClaw es **basado en Docker Compose**: el repositorio ya incluye un `docker-compose.yml`, así que no tienes que escribirlo a mano. El flujo oficial clona el repo y corre el script de setup, que levanta el contenedor con ese compose y te pide las API keys: ```bash git clone https://github.com/openclaw/openclaw.git cd openclaw # Usar la imagen precompilada (GitHub Container Registry) export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest" # Levanta el contenedor y corre el onboarding ./scripts/docker/setup.sh ``` El script te pregunta las **claves de API** del proveedor, configura el `docker-compose.yml` y deja el contenedor corriendo. La interfaz de control queda en **http://127.0.0.1:18789/**. > Si prefieres construir la imagen localmente en vez de bajarla, omite el `export OPENCLAW_IMAGE` y el setup compila `openclaw:local` desde el código del repo. ## Dónde se guardan tus datos Docker Compose monta la configuración en volúmenes, así que tus datos **sobreviven** a recrear o actualizar el contenedor. Los directorios que persiste: - `OPENCLAW_CONFIG_DIR` → `/home/node/.openclaw` — configuración del agente - `OPENCLAW_WORKSPACE_DIR` → `/home/node/.openclaw/workspace` — espacio de trabajo - `OPENCLAW_AUTH_PROFILE_SECRET_DIR` → `/home/node/.config/openclaw` — secretos de autenticación Mientras no borres esos volúmenes, puedes destruir y recrear el contenedor sin perder la configuración ni las claves. ## Actualizar OpenClaw en Docker Una de las ventajas del compose: actualizar es bajar la imagen nueva y recrear el contenedor, sin tocar la configuración (vive en los volúmenes): ```bash cd openclaw docker compose pull docker compose up -d ``` ## Comandos útiles ```bash docker compose ps # estado del contenedor docker compose logs -f # logs en vivo docker compose down # detener (conserva los volúmenes) docker compose up -d # volver a levantar ``` ## Docker en Raspberry Pi La imagen funciona en una Pi de 64 bits siempre que sea multi-arquitectura (ARM64). Dicho eso, en una Raspberry Pi el script nativo con Node 22 suele ser más liviano que sumar la capa de Docker. El detalle completo —qué modelo sirve y cómo dejarlo 24/7— está en la [guía de OpenClaw en Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi). ## Preguntas frecuentes ### ¿Cuál es la imagen oficial de OpenClaw en Docker? `ghcr.io/openclaw/openclaw:latest`, alojada en GitHub Container Registry. También puedes construir `openclaw:local` desde el repositorio. ### ¿Necesito escribir un docker-compose.yml? No. El repositorio ya trae el `docker-compose.yml`; el script `./scripts/docker/setup.sh` lo usa por ti. ### ¿Pierdo mi configuración al actualizar el contenedor? No, siempre que no borres los volúmenes. La config vive en `/home/node/.openclaw` y se persiste fuera del contenedor. ### ¿En qué puerto queda OpenClaw? El gateway escucha en `127.0.0.1:18789`. Para llegar desde otra máquina, mejor un túnel (por ejemplo Cloudflare Tunnel) que abrir el puerto. ## Cierre Con Docker Compose, OpenClaw queda aislado, reproducible y trivial de actualizar (`docker compose pull && up -d`). Para las demás formas de instalarlo —script, npm, nativo por plataforma— está la [guía completa de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Fuente - [Documentación oficial de OpenClaw — Docker](https://docs.openclaw.ai/install/docker) --- ### Cómo instalar OpenClaw en una Raspberry Pi (4 y 5) - URL: https://www.angelcruz.dev/post/instalar-openclaw-en-raspberry-pi - Markdown: https://www.angelcruz.dev/post/instalar-openclaw-en-raspberry-pi.md - Categoría: OpenClaw - Fecha: 2026-06-16 - Excerpt: Guía paso a paso para correr OpenClaw 24/7 en una Raspberry Pi: qué modelo sirve de verdad, Raspberry Pi OS 64-bit, instalación, daemon con systemd y acceso remoto con Cloudflare Tunnel. --- title: "Cómo instalar OpenClaw en una Raspberry Pi (4 y 5)" excerpt: "Guía paso a paso para correr OpenClaw 24/7 en una Raspberry Pi: qué modelo sirve de verdad, Raspberry Pi OS 64-bit, instalación, daemon con systemd y acceso remoto con Cloudflare Tunnel." date: "2026-06-16" lastModified: "2026-06-16" category: "OpenClaw" tech_article: true seo_title: "Instalar OpenClaw en Raspberry Pi 4 y 5 (y por qué no la 3)" seo_description: "Corre OpenClaw 24/7 en una Raspberry Pi: qué modelo sirve (Pi 4 de 4GB+ o Pi 5; la Pi 3 no), OS de 64 bits, instalación, daemon con systemd y acceso remoto con Cloudflare Tunnel." author: name: "Angel Cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- Una Raspberry Pi es el lugar ideal para [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source): un equipo de bajo consumo, siempre encendido, en tu propia red. El asistente queda corriendo como un servicio 24/7 sin que tengas que dejar la laptop prendida, y tus datos nunca salen de casa. Esta guía es el camino específico para Raspberry Pi. Si quieres el panorama completo (macOS, Windows, Docker), está en la [guía de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa); aquí voy directo a lo que cambia en la Pi. ## Qué Raspberry Pi necesitas (la verdad sobre el hardware) OpenClaw pide **Node.js 22+** y al menos **4 GB de RAM** (8 GB para uso intensivo). Ese requisito de memoria es el que decide qué Pi sirve: | Modelo | RAM | ¿Sirve? | | --- | --- | --- | | Raspberry Pi 5 (4/8/16 GB) | 4–16 GB | ✅ La mejor opción | | Raspberry Pi 4 (4 u 8 GB) | 4–8 GB | ✅ Mínimo viable (la de 4 GB puede usar swap) | | Raspberry Pi 4 (2 GB) | 2 GB | ❌ Por debajo del mínimo | | Raspberry Pi 3 / 3B+ | 1 GB | ❌ No alcanza | | Raspberry Pi Zero 2 W | 512 MB | ❌ No alcanza | Lo digo claro porque mucha gente busca "OpenClaw en Raspberry Pi 3": **la Pi 3 no llega**, tiene como máximo 1 GB de RAM y el mínimo es 4 GB. La **Pi 4 de 4 GB** es el piso real (ya usa swap bajo carga); lo cómodo es una **Pi 5 con 8 GB**. > No intentes correr modelos de IA **locales** en la Pi: no tiene GPU para eso. La Pi ejecuta el agente OpenClaw, pero la inferencia la hace una API en la nube (Claude, OpenAI). Eso mantiene la Pi liviana y la cuenta de luz baja. ## Paso 1: Raspberry Pi OS de 64 bits Node.js 22 necesita un sistema de **64 bits**. Instalá **Raspberry Pi OS (64-bit)** con Raspberry Pi Imager. Como OpenClaw corre headless (sin monitor), te conviene la versión **Lite**: menos peso, sin escritorio. En el Imager, antes de grabar la SD, abre la configuración avanzada (⚙️) y activa **SSH**, el usuario y el Wi-Fi. Así arrancas sin teclado ni pantalla. Una vez booteada, conéctate por SSH y actualiza: ```bash ssh tu-usuario@raspberrypi.local sudo apt update && sudo apt full-upgrade -y sudo reboot ``` Confirmá que es 64 bits (debe decir `aarch64`): ```bash uname -m ``` ## Paso 2: Instalar OpenClaw El script oficial detecta la arquitectura ARM e instala Node 22 por ti. Es la vía recomendada en la Pi: ```bash curl -fsSL https://openclaw.ai/install.sh | bash ``` Si prefieres hacerlo a mano, primero necesitas Node 22+ para ARM64 (el `apt` de Raspberry Pi OS suele traer una versión vieja). La forma más limpia es con [fnm](https://github.com/Schniz/fnm) o nvm: ```bash # Instalar fnm y Node 22 curl -fsSL https://fnm.vercel.app/install | bash exec $SHELL fnm install 22 && fnm use 22 # Verificar node --version # v22.x o superior # Instalar OpenClaw npm install -g openclaw@latest ``` ## Paso 3: Dejarlo corriendo 24/7 con systemd Acá está el sentido de usar una Pi: que OpenClaw quede como servicio permanente. El wizard de onboarding lo registra como servicio **systemd** con un solo flag: ```bash openclaw onboard --install-daemon ``` El wizard te va a pedir la **API key** del proveedor de IA, el puerto del gateway (por defecto `18789`) y los canales que quieras conectar (Telegram, Slack, Discord, WhatsApp). Al terminar, el agente sobrevive reinicios de la Pi. Comandos útiles para administrarlo: ```bash openclaw status # ¿está activo el daemon? openclaw --version # versión instalada systemctl status openclaw # estado del servicio systemd journalctl -u openclaw -f # ver logs en vivo ``` ## Paso 4: Acceso remoto con Cloudflare Tunnel El gateway de OpenClaw escucha en `127.0.0.1:18789`, o sea solo dentro de la Pi. Para llegar desde afuera sin abrir puertos en tu router (mala idea exponer tu casa a internet), lo más seguro es un **Cloudflare Tunnel**: una conexión saliente cifrada, sin IP pública ni port-forwarding. ```bash # Instalar cloudflared (ARM64) curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64 -o cloudflared sudo install cloudflared /usr/local/bin/ # Autenticar y crear el túnel cloudflared tunnel login cloudflared tunnel --url http://localhost:18789 ``` Cloudflare te devuelve una URL pública (o puedes mapear un subdominio tuyo) que apunta al gateway de la Pi, con HTTPS y sin exponer tu red. Es el patrón que recomienda la comunidad para acceder a OpenClaw en la Pi desde el celular. ## ¿Y Docker en la Pi? OpenClaw también corre en Docker, y en una Pi de 64 bits funciona siempre que la imagen sea multi-arquitectura (ARM64). Si ya manejas contenedores, el flujo con `docker-compose` es el mismo que en cualquier Linux —lo cubro en la [sección de Docker de la guía principal](/post/como-instalar-openclaw-guia-completa). Para la mayoría de las Pi, el script o npm con un Node 22 nativo es más liviano que sumar la capa de Docker. ## Preguntas frecuentes ### ¿Funciona OpenClaw en una Raspberry Pi 3? No de forma usable. La Pi 3 tiene como máximo 1 GB de RAM y OpenClaw necesita al menos 4 GB. Lo mínimo recomendable es una Pi 4 de 4 GB. ### ¿Cuánta RAM necesita OpenClaw en la Pi? 4 GB para funcionar, 8 GB para uso intensivo (varios canales y skills activos a la vez). ### ¿Consume mucha energía dejarla 24/7? No: una Raspberry Pi 4 o 5 consume unos pocos vatios, mucho menos que dejar una computadora encendida. Por eso es el equipo ideal para un agente siempre activo. ### ¿Tengo que dejar la laptop prendida? No. Esa es la gracia: el agente vive en la Pi. Apagas la laptop y OpenClaw sigue revisando emails, respondiendo en tus canales y ejecutando tus skills. ## Cierre Una Pi 5 (o Pi 4 de 4 GB+) con Raspberry Pi OS de 64 bits, OpenClaw como servicio systemd y un Cloudflare Tunnel para el acceso remoto: ese es el setup completo de un asistente de IA privado, siempre encendido y de bajo consumo. Si todavía no decidiste *qué* hacer con él, en la [guía completa de OpenClaw](/post/como-instalar-openclaw-guia-completa) están las demás plataformas y la configuración de canales y skills. ## Fuente - [No Mac Mini, no worries: running OpenClaw on a Raspberry Pi](https://ajfisher.me/2026/02/03/openclaw-raspberrypi-howto/) — howto de referencia de ajfisher (Pi 5 8GB, mínimo Pi 4 4GB, Raspberry Pi OS Lite 64-bit, Node 22). --- ### Cómo instalar OpenClaw en Ubuntu (servidor 24/7) - URL: https://www.angelcruz.dev/post/instalar-openclaw-en-ubuntu - Markdown: https://www.angelcruz.dev/post/instalar-openclaw-en-ubuntu.md - Categoría: OpenClaw - Fecha: 2026-06-16 - Excerpt: Instala OpenClaw en Ubuntu paso a paso: el script automático o Node manual, cómo dejarlo corriendo como servicio systemd 24/7, requisitos y los comandos para administrarlo. --- title: "Cómo instalar OpenClaw en Ubuntu (servidor 24/7)" excerpt: "Instala OpenClaw en Ubuntu paso a paso: el script automático o Node manual, cómo dejarlo corriendo como servicio systemd 24/7, requisitos y los comandos para administrarlo." date: "2026-06-16" lastModified: "2026-06-16" category: "OpenClaw" tech_article: true seo_title: "Instalar OpenClaw en Ubuntu paso a paso (2026)" seo_description: "Cómo instalar OpenClaw en Ubuntu: script automático o Node manual, configurarlo como servicio systemd 24/7 y administrar el daemon. Requisitos y comandos verificados." author: name: "Angel Cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- Ubuntu es de los mejores lugares para correr [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source): lo dejas como servicio del sistema, sobrevive reinicios y queda activo sin que tengas que abrir nada. Esta guía es el camino específico para Ubuntu; si buscas el panorama completo (macOS, Windows, Docker), está en la [guía de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Requisitos - **Ubuntu de 64 bits** (20.04, 22.04 o 24.04 LTS) - **Node.js 22.19+** (la documentación recomienda Node 24); el script lo instala por ti - Al menos **4 GB de RAM** (8 GB para uso intensivo) - Una **API key** de tu proveedor de IA (Claude, OpenAI o un modelo local) ## Opción A: script automático (recomendado) La forma más rápida. El script detecta Ubuntu, instala Node si hace falta y deja OpenClaw listo: ```bash curl -fsSL https://openclaw.ai/install.sh | bash ``` Al terminar arranca el wizard de onboarding, que te pide la API key, el puerto del gateway (`18789` por defecto) y los canales que quieras conectar. ## Opción B: instalación manual con npm Si prefieres controlar la versión de Node, instálalo primero. El `apt` de Ubuntu suele traer una versión vieja, así que lo más limpio es [fnm](https://github.com/Schniz/fnm): ```bash # Instalar fnm y Node 24 curl -fsSL https://fnm.vercel.app/install | bash exec $SHELL fnm install 24 && fnm use 24 node --version # v24.x (o v22.19+) # Instalar OpenClaw npm install -g openclaw@latest ``` ## Dejarlo corriendo 24/7 con systemd Aquí está la ventaja de Ubuntu como servidor: registrar OpenClaw como servicio systemd con un flag, para que arranque solo y sobreviva reinicios: ```bash openclaw onboard --install-daemon ``` El wizard te pide la API key y los canales; el flag `--install-daemon` registra el servicio systemd. Para administrarlo: ```bash openclaw status # ¿está activo el daemon? systemctl status openclaw # estado del servicio journalctl -u openclaw -f # logs en vivo openclaw --version # versión instalada ``` ## Acceso al gateway La interfaz de control queda en **http://127.0.0.1:18789/**, accesible solo desde el propio servidor. Para llegar desde afuera **no abras el puerto en el firewall**: usa un túnel saliente (por ejemplo Cloudflare Tunnel, el mismo patrón que detallo en la [guía de Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi)). Es más seguro y no expone tu servidor. ## Preguntas frecuentes ### ¿Qué versión de Ubuntu necesito? Cualquiera de 64 bits razonablemente reciente (20.04 LTS en adelante). En un servidor, la versión headless va perfecta; no hace falta entorno de escritorio. ### ¿Server o Desktop? Para un agente siempre activo, Ubuntu Server (headless) es lo ideal: menos recursos y pensado para correr 24/7. ### ¿Cuánta RAM consume? El mínimo recomendado son 4 GB; con varios canales y skills activos, apunta a 8 GB. ## Cierre Con el script de instalación y `--install-daemon`, OpenClaw queda como un servicio systemd permanente en tu Ubuntu, accesible en el puerto 18789 y listo para conectarse a tus canales. Para Docker, Windows o la configuración de skills, está la [guía completa de OpenClaw](/post/como-instalar-openclaw-guia-completa). --- ### Cómo instalar OpenClaw en Windows (nativo o WSL2) - URL: https://www.angelcruz.dev/post/instalar-openclaw-en-windows - Markdown: https://www.angelcruz.dev/post/instalar-openclaw-en-windows.md - Categoría: OpenClaw - Fecha: 2026-06-16 - Excerpt: OpenClaw en Windows 10 y 11 tiene tres caminos oficiales: el instalador de PowerShell, la app nativa Windows Hub o un gateway en WSL2. Cuándo conviene cada uno, paso a paso. --- title: "Cómo instalar OpenClaw en Windows (nativo o WSL2)" excerpt: "OpenClaw en Windows 10 y 11 tiene tres caminos oficiales: el instalador de PowerShell, la app nativa Windows Hub o un gateway en WSL2. Cuándo conviene cada uno, paso a paso." date: "2026-06-16" lastModified: "2026-06-16" category: "OpenClaw" tech_article: true seo_title: "Instalar OpenClaw en Windows: PowerShell, app nativa o WSL2" seo_description: "Instala OpenClaw en Windows 10/11 por sus 3 vías oficiales: instalador de PowerShell, la app nativa Windows Hub o WSL2. No necesitas solo WSL2. Requisitos y pasos." author: name: "Angel Cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- Mucha gente cree que [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source) en Windows solo corre dentro de WSL2. No es así: hoy tiene **tres caminos oficiales**, dos de ellos nativos. Esta guía cubre los tres y cuándo conviene cada uno; para el resto de plataformas, está la [guía de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Requisitos - **Windows 10 (20H2 o superior)** o **Windows 11** - **Node.js 22.19+** (la documentación recomienda Node 24); el instalador lo resuelve por ti - Al menos **4 GB de RAM** (8 GB para uso intensivo) - Una **API key** de tu proveedor de IA ## Camino 1: instalador de PowerShell (recomendado) El más directo. Abre **PowerShell** y ejecuta: ```powershell iwr -useb https://openclaw.ai/install.ps1 | iex ``` El script instala Node si hace falta, deja OpenClaw listo y arranca el onboarding (API key, puerto del gateway, canales). Corre nativo, sin WSL2. ## Camino 2: app nativa Windows Hub Para usuarios de escritorio, la documentación oficial ofrece la **app nativa Windows Hub**, que incluye setup, estado en la bandeja del sistema (tray), chat, modo nodo y modo MCP local. Es la opción más cómoda si prefieres una interfaz gráfica en vez de la línea de comandos. ## Camino 3: gateway en WSL2 Si ya trabajas en **WSL2** y quieres el entorno Linux (con el daemon systemd y todo), instala una Ubuntu dentro de WSL2 y sigue el camino Linux: es idéntico al de la [guía de OpenClaw en Ubuntu](/post/instalar-openclaw-en-ubuntu). Esta vía es la que conviene si quieres tratar OpenClaw como un servicio de servidor. ## ¿Cuál elegir? | Camino | Para quién | | --- | --- | | Instalador PowerShell | La mayoría; rápido y nativo | | App Windows Hub | Quien prefiere interfaz gráfica y bandeja del sistema | | WSL2 | Quien quiere el entorno Linux y el daemon systemd | En todos los casos, la interfaz de control queda en **http://127.0.0.1:18789/**. ## Preguntas frecuentes ### ¿Necesito WSL2 para correr OpenClaw en Windows? No. WSL2 es una de las tres opciones, pero el instalador de PowerShell y la app nativa Windows Hub corren directo en Windows, sin WSL2. ### ¿Qué versión de Windows necesito? Windows 10 20H2 o superior, o Windows 11. ### ¿Qué Node.js requiere? Node 22.19 o superior; la documentación recomienda Node 24. El instalador se encarga de instalarlo. ## Cierre En Windows tienes tres caminos: PowerShell para lo más rápido, la app Windows Hub para una experiencia gráfica, y WSL2 si quieres el entorno Linux. Para Docker, Ubuntu o macOS, está la [guía completa de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Fuente - [Documentación oficial de OpenClaw — instalación](https://docs.openclaw.ai/install) --- ### Null coalescing en PHP: el operador ?? y ??= - URL: https://www.angelcruz.dev/post/null-coalescing-php - Markdown: https://www.angelcruz.dev/post/null-coalescing-php.md - Categoría: PHP - Fecha: 2026-06-16 - Excerpt: Cómo funciona el operador null coalescing (??) y su versión de asignación (??=) en PHP: valores por defecto sin warnings, encadenamiento, y la diferencia clave con el operador Elvis (?:). --- title: "Null coalescing en PHP: el operador ?? y ??=" excerpt: "Cómo funciona el operador null coalescing (??) y su versión de asignación (??=) en PHP: valores por defecto sin warnings, encadenamiento, y la diferencia clave con el operador Elvis (?:)." date: "2026-06-16T00:00:00.000Z" lastModified: "2026-06-16T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "Null coalescing en PHP: el operador ?? y ??= con ejemplos" seo_description: "El operador null coalescing (??) de PHP devuelve un valor por defecto cuando una variable es null o no existe, sin warnings. Aprende ??, ??= y la diferencia con el Elvis (?:)." tech_article: article_section: "PHP Fundamentals" keywords: "null coalescing php, operador ?? php, operador de fusion null php, ??= php, valor por defecto php" is_part_of: url: "https://www.angelcruz.dev/post/operadores-php" --- El operador **null coalescing** (`??`), o de fusión de null, devuelve su operando izquierdo si **existe y no es `null`**; de lo contrario, devuelve el derecho. Sirve para dar valores por defecto sin que PHP lance un warning cuando la variable no está definida. Desde PHP 7.4 existe además `??=`, que asigna el valor por defecto solo cuando la variable es `null` o no existe. Este artículo forma parte de la [guía de operadores en PHP](/post/operadores-php). Aquí me enfoco en `??` y `??=` y en cómo se diferencian del Elvis. ## El problema que resuelve `??` Leer una clave que puede no existir —de `$_GET`, de un array de configuración, de una respuesta de API— obligaba a verificar con `isset()` para evitar el warning *"Undefined array key"*: ```php // Antes: verificación manual con isset $nombre = isset($_GET['nombre']) ? $_GET['nombre'] : 'Invitado'; // Con null coalescing: mismo resultado, sin warning $nombre = $_GET['nombre'] ?? 'Invitado'; ``` `$_GET['nombre'] ?? 'Invitado'` equivale exactamente a `isset($_GET['nombre']) ? $_GET['nombre'] : 'Invitado'`, pero en una expresión limpia. Si la clave no existe o es `null`, devuelve `'Invitado'` sin quejarse. ## Encadenamiento: el primer valor que exista `??` se puede encadenar. PHP evalúa de izquierda a derecha y devuelve el **primer operando que no sea `null`**: ```php // Busca el tema en preferencias del usuario, luego en config global, luego un default $tema = $userConfig['theme'] ?? $appConfig['theme'] ?? 'dark'; ``` Es uno de los patrones más útiles de PHP 7+: una sola línea recorre varias fuentes de fallback en orden de prioridad. ## `??=` : asignar solo si está vacío El operador de **asignación coalescente** (`??=`, PHP 7.4+) asigna el valor de la derecha **solo si** la variable de la izquierda es `null` o no existe. Si ya tiene un valor, lo deja intacto: ```php $opciones['timeout'] ??= 30; // Asigna 30 solo si 'timeout' no estaba definido o era null. // Equivale a: $opciones['timeout'] = $opciones['timeout'] ?? 30; ``` Es ideal para rellenar valores por defecto en un array de configuración sin pisar lo que el usuario ya definió. ## La diferencia clave: `??` vs `?:` (Elvis) Aquí es donde la mayoría se confunde. El [operador Elvis (`?:`)](/post/el-operador-ternario-php) y el null coalescing (`??`) parecen lo mismo, pero **chequean cosas distintas**: - **`?:`** evalúa *truthiness*: dispara el fallback con cualquier valor *falsy* (`""`, `0`, `"0"`, `[]`, `false`, `null`). - **`??`** solo mira si el valor es `null` o no existe. Un `""` o un `0` se consideran valores válidos y **no** disparan el fallback. ```php $valor = ""; echo $valor ?: 'fallback'; // "fallback" — "" es falsy echo $valor ?? 'fallback'; // "" — "" no es null, es un valor válido ``` | Situación | `?:` (Elvis) | `??` (null coalescing) | |---|---|---| | Variable no definida | warning + fallback | fallback, **sin** warning | | Valor `""` o `0` | fallback (es *falsy*) | devuelve `""` o `0` | | Valor `null` | fallback | fallback | La regla: en formularios y datos donde `""` o `0` son valores legítimos, usa `??`. Solo usa `?:` cuando de verdad quieres tratar lo *falsy* como "vacío". ## Combinarlo con el operador nullsafe `?->` Desde PHP 8, el operador nullsafe (`?->`) corta una cadena de propiedades o métodos si algo es `null`, en vez de tirar un error. Combinado con `??`, da una expresión muy expresiva para navegar objetos que pueden no existir: ```php // Si $usuario, su dirección o su ciudad son null, el resultado es 'desconocida' $ciudad = $usuario?->direccion?->ciudad ?? 'desconocida'; ``` El `?->` evita el error al acceder a propiedades de `null`, y el `??` provee el valor por defecto final. ## Preguntas frecuentes ### ¿Qué hace el operador `??` en PHP? Devuelve su operando izquierdo si existe y no es `null`; en caso contrario, devuelve el derecho. Sirve para asignar valores por defecto sin generar warnings: `$nombre = $_GET['user'] ?? 'anónimo';`. ### ¿Cuál es la diferencia entre `??` y `?:`? `?:` (Elvis) dispara el fallback con cualquier valor *falsy* (`""`, `0`, `null`…), mientras que `??` solo lo hace cuando el valor es `null` o no existe. Además, `??` no genera warning si la variable no está definida y `?:` sí. ### ¿Qué es `??=` en PHP? Es el operador de asignación coalescente (PHP 7.4+). `$a ??= $b` asigna `$b` a `$a` solo si `$a` es `null` o no existe. Equivale a `$a = $a ?? $b` pero sin repetir la variable. ### ¿`??` genera un warning si la variable no existe? No. Esa es justamente su ventaja sobre `isset()` manual o el Elvis: accede a la variable de forma segura y devuelve el fallback sin emitir *"Undefined variable"* ni *"Undefined array key"*. ## Cierre El null coalescing es el operador al que recurres cada vez que un valor puede faltar: `??` para leer con un default seguro, `??=` para rellenar sin pisar, y encadenado para recorrer fuentes en orden de prioridad. La clave es no confundirlo con el Elvis: `??` mira `null`, `?:` mira *truthiness*. Para el resto de operadores del lenguaje, vuelve a la [guía de operadores en PHP](/post/operadores-php). --- ### Operador spaceship (<=>) en PHP - URL: https://www.angelcruz.dev/post/operador-spaceship-php - Markdown: https://www.angelcruz.dev/post/operador-spaceship-php.md - Categoría: PHP - Fecha: 2026-06-16 - Excerpt: Qué es el operador nave espacial (<=>) de PHP, qué devuelve y su uso estrella: ordenar arrays con usort. Orden ascendente, descendente y por múltiples criterios, con ejemplos. --- title: "Operador spaceship (<=>) en PHP" excerpt: "Qué es el operador nave espacial (<=>) de PHP, qué devuelve y su uso estrella: ordenar arrays con usort. Orden ascendente, descendente y por múltiples criterios, con ejemplos." date: "2026-06-16T00:00:00.000Z" lastModified: "2026-06-16T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "Operador spaceship (<=>) en PHP: qué es y cómo ordenar" seo_description: "El operador spaceship (<=>) de PHP compara dos valores y devuelve -1, 0 o 1. Aprende a usarlo con usort para ordenar arrays ascendente, descendente y por varios criterios." tech_article: article_section: "PHP Fundamentals" keywords: "operador spaceship php, operador nave espacial php, usort php, ordenar arrays php, comparar valores php" is_part_of: url: "https://www.angelcruz.dev/post/operadores-php" --- El **operador spaceship** (`<=>`), o nave espacial, llegó con PHP 7 y compara dos valores devolviendo un entero: **menor que cero** si el izquierdo es menor, **cero** si son iguales, **mayor que cero** si el izquierdo es mayor (en la práctica, `-1`, `0` o `1`). Su uso principal es ordenar arrays con `usort` en una sola línea. Este artículo forma parte de la [guía de operadores en PHP](/post/operadores-php). Aquí me enfoco en cómo funciona y para qué sirve de verdad. ## Qué devuelve `<=>` ```php echo 1 <=> 2; // -1 (izquierdo menor) echo 2 <=> 2; // 0 (iguales) echo 3 <=> 2; // 1 (izquierdo mayor) echo "a" <=> "b"; // -1 (orden alfabético) echo 1.5 <=> 1.5; // 0 ``` La especificación dice "un entero menor, igual o mayor que cero", no necesariamente `-1`/`1`. En la práctica PHP devuelve `-1`, `0` o `1`, pero tu código no debería depender del valor exacto: lo que importa es el **signo**. El spaceship usa las mismas reglas de comparación que `<`, `>` y compañía. Compara números como números, strings de forma lexicográfica, y sigue las reglas estándar de PHP para tipos mixtos. **No es para comprobar igualdad** (para eso está [`===`](/post/igual-vs-identico-php)): es para *ordenar*. ## El motivo de existir: ordenar con `usort` `usort` necesita una función que, dados dos elementos, diga cuál va primero devolviendo un número negativo, cero o positivo. Eso es exactamente lo que produce el spaceship. Antes de PHP 7, esa función era verbosa: ```php // Antes de PHP 7 usort($numeros, function ($a, $b) { if ($a === $b) return 0; return ($a < $b) ? -1 : 1; }); // Con el spaceship: una línea usort($numeros, fn($a, $b) => $a <=> $b); ``` ### Orden ascendente y descendente La dirección del orden depende de qué operando va a la izquierda. Para invertirlo, invierte los operandos: ```php $numeros = [3, 1, 4, 1, 5, 9, 2, 6]; usort($numeros, fn($a, $b) => $a <=> $b); // Ascendente: [1, 1, 2, 3, 4, 5, 6, 9] usort($numeros, fn($a, $b) => $b <=> $a); // Descendente: [9, 6, 5, 4, 3, 2, 1, 1] ``` ### Ordenar arrays de datos por un campo El caso real más común: ordenar una lista de registros por una propiedad. ```php $usuarios = [ ['nombre' => 'Ana', 'edad' => 30], ['nombre' => 'Luis', 'edad' => 25], ['nombre' => 'Eva', 'edad' => 35], ]; usort($usuarios, fn($a, $b) => $a['edad'] <=> $b['edad']); // Por edad ascendente: Luis (25), Ana (30), Eva (35) ``` Funciona igual con objetos: `fn($a, $b) => $a->edad <=> $b->edad`. ### Ordenar por varios criterios Aquí brilla la combinación con el [operador Elvis (`?:`)](/post/el-operador-ternario-php). Como `<=>` devuelve `0` cuando los valores son iguales (y `0` es *falsy*), el `?:` deja pasar la comparación a la siguiente: ```php $personas = [ ['apellido' => 'Pérez', 'nombre' => 'Ana'], ['apellido' => 'García', 'nombre' => 'Luis'], ['apellido' => 'Pérez', 'nombre' => 'Carlos'], ]; usort($personas, fn($a, $b) => $a['apellido'] <=> $b['apellido'] // primero por apellido ?: $a['nombre'] <=> $b['nombre'] // si empatan, desempata por nombre ); // García/Luis, Pérez/Ana, Pérez/Carlos ``` Si el apellido es igual, la primera comparación da `0` y el `?:` evalúa la segunda. Puedes encadenar tantos criterios como necesites. ## Spaceship con strings, arrays y objetos ```php // Strings: comparación lexicográfica echo "manzana" <=> "naranja"; // -1 // Arrays: primero por cantidad de elementos, luego elemento a elemento echo [1, 2, 3] <=> [1, 2, 3]; // 0 echo [1, 2, 3] <=> [1, 2, 1]; // 1 // Objetos: compara los atributos (deben ser de la misma clase) ``` Con strings numéricas aplican las reglas de comparación de PHP 8 (dos strings numéricas se comparan como números), igual que con `==`. Si necesitas un orden de strings que no dependa de eso, conviértelas a un tipo explícito antes de comparar. ## Preguntas frecuentes ### ¿Qué devuelve el operador `<=>` en PHP? Un entero: menor que cero si el operando izquierdo es menor, cero si son iguales, y mayor que cero si el izquierdo es mayor. En la práctica, `-1`, `0` o `1`. Lo relevante es el signo, no el valor exacto. ### ¿Para qué sirve el operador spaceship? Sobre todo para escribir funciones de comparación en `usort`, `uasort` y `uksort`. Reemplaza el típico bloque `if-else` que devolvía `-1`, `0` o `1` por una sola expresión. ### ¿Cómo ordeno de mayor a menor con el spaceship? Invierte los operandos: `fn($a, $b) => $b <=> $a` ordena descendente, mientras que `$a <=> $b` ordena ascendente. ### ¿Sirve `<=>` para comparar igualdad? No. Para saber si dos valores son iguales usa `==` o `===`. El spaceship está pensado para *ordenar*, no para condiciones booleanas. Repaso la diferencia en [`==` vs `===`](/post/igual-vs-identico-php). ## Cierre El operador spaceship convierte la tarea repetitiva de escribir funciones de comparación en una sola expresión legible: `$a <=> $b` para ascendente, `$b <=> $a` para descendente, y `?:` encadenados para ordenar por varios criterios. Es un operador de nicho —vive casi siempre dentro de `usort`—, pero cuando lo necesitas, ahorra mucho ruido. Para el resto de operadores del lenguaje, vuelve a la [guía de operadores en PHP](/post/operadores-php). --- ### Operadores en PHP: la guía completa - URL: https://www.angelcruz.dev/post/operadores-php - Markdown: https://www.angelcruz.dev/post/operadores-php.md - Categoría: PHP - Fecha: 2026-06-16 - Excerpt: Todos los operadores de PHP en un solo lugar: aritméticos, de asignación, de comparación, lógicos, de cadena, de incremento, bit a bit, ternario y null coalescing, con ejemplos y los gotchas que más confunden. --- title: "Operadores en PHP: la guía completa" excerpt: "Todos los operadores de PHP en un solo lugar: aritméticos, de asignación, de comparación, lógicos, de cadena, de incremento, bit a bit, ternario y null coalescing, con ejemplos y los gotchas que más confunden." date: "2026-06-16T00:00:00.000Z" lastModified: "2026-06-16T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "Operadores en PHP: tipos, sintaxis y ejemplos (2026)" seo_description: "Guía de los operadores de PHP con ejemplos: aritméticos, de comparación (== vs ===), lógicos, ternario, null coalescing (??) y el spaceship (<=>). Con gotchas reales." tech_article: article_section: "PHP Fundamentals" keywords: "operadores php, operadores en php, operadores de comparacion php, operadores logicos php, null coalescing php, operador spaceship php" --- Un **operador** en PHP es un símbolo que toma uno o más valores (los *operandos*) y produce otro. Sumar, comparar, asignar, concatenar texto: todo pasa por operadores. Esta guía los reúne todos por categoría, con ejemplos cortos y los detalles donde la mayoría tropieza. Para los temas con más tela —el operador ternario, por ejemplo— hay artículos dedicados enlazados desde cada sección. ## Tabla de referencia rápida | Categoría | Operadores | |---|---| | Aritméticos | `+` `-` `*` `/` `%` `**` | | Asignación | `=` `+=` `-=` `*=` `/=` `%=` `**=` `.=` `??=` | | Comparación | `==` `===` `!=` `<>` `!==` `<` `>` `<=` `>=` `<=>` | | Lógicos | `&&` `||` `!` `and` `or` `xor` | | De cadena | `.` `.=` | | Incremento / decremento | `++` `--` | | Bit a bit | `&` `|` `^` `~` `<<` `>>` | | Ternario y null coalescing | `? :` `?:` `??` `??=` | ## Operadores aritméticos Los de toda la vida, más dos que conviene tener presentes: ```php $a = 10; $b = 3; echo $a + $b; // 13 echo $a - $b; // 7 echo $a * $b; // 30 echo $a / $b; // 3.3333333333333 echo $a % $b; // 1 (resto de la división entera) echo $a ** $b; // 1000 (exponenciación: 10 elevado a 3) ``` Dos detalles que sorprenden: - **`%` trabaja con enteros.** Convierte ambos operandos a `int` antes de operar. Si necesitas el resto de una división con decimales, usa `fmod($a, $b)`. - **`**` es asociativo por la derecha.** `2 ** 3 ** 2` se evalúa como `2 ** (3 ** 2)`, o sea `2 ** 9 = 512`, no `64`. ## Operadores de asignación Además del `=` clásico, cada operador aritmético o de cadena tiene su versión combinada, que opera y asigna en un paso: ```php $total = 100; $total += 50; // 150 (equivale a $total = $total + 50) $total -= 20; // 130 $total *= 2; // 260 $total .= " USD"; // "260 USD" (concatena y asigna) $config ??= 'valor por defecto'; // asigna solo si $config es null o no existe ``` El último, `??=` (asignación coalescente, desde PHP 7.4), es de los más útiles: asigna el valor de la derecha **solo si** la variable de la izquierda es `null` o no está definida. Equivale a `$config = $config ?? 'valor por defecto';` pero sin repetir la variable. ## Operadores de comparación Aquí vive el malentendido más común de PHP: la diferencia entre `==` y `===`. ```php var_dump(5 == "5"); // true — compara el valor tras convertir tipos var_dump(5 === "5"); // false — exige mismo valor Y mismo tipo var_dump(5 != "6"); // true var_dump(5 !== "5"); // true — distinto tipo, así que no son idénticos ``` - **`==` (igual)** compara valores aplicando *conversión de tipos*. `5 == "5"` es `true` porque PHP ajusta los tipos antes de comparar. - **`===` (idéntico)** exige el mismo valor **y** el mismo tipo. Es la comparación segura y la que deberías usar por defecto. Esta distinción tiene más tela de la que parece —el cambio de PHP 8, los gotchas de `switch` e `in_array()`, cómo comparar arrays y objetos—, así que la desarrollo aparte en **[`==` vs `===` en PHP](/post/igual-vs-identico-php)**. ### El operador spaceship `<=>` Desde PHP 7, el operador nave espacial compara dos valores y devuelve un entero: **menor que cero** si el izquierdo es menor, **cero** si son iguales, **mayor que cero** si el izquierdo es mayor (en la práctica, `-1`, `0` o `1`). ```php echo 1 <=> 2; // -1 echo 2 <=> 2; // 0 echo 3 <=> 2; // 1 // Su uso estrella: ordenar sin escribir la lógica de comparación a mano $numeros = [3, 1, 2]; usort($numeros, fn($a, $b) => $a <=> $b); // [1, 2, 3] ``` Si vas a ordenar de verdad —arrays de datos, varios criterios, ascendente y descendente—, lo cubro a fondo en **[el operador spaceship (`<=>`) en PHP](/post/operador-spaceship-php)**. ## Operadores lógicos Combinan expresiones booleanas. Hay dos juegos: los símbolos (`&&`, `||`, `!`) y las palabras (`and`, `or`, `xor`). ```php $activo = true; $verificado = false; var_dump($activo && $verificado); // false — ambos deben ser true var_dump($activo || $verificado); // true — basta con uno var_dump(!$verificado); // true — niega el valor var_dump($activo xor $verificado);// true — true solo si difieren ``` **El gotcha de `and` / `or`:** no son intercambiables con `&&` / `||`. Las palabras tienen **menor precedencia que `=`**, así que esto no hace lo que parece: ```php $resultado = true and false; var_dump($resultado); // true (!) // Se evalúa como: ($resultado = true) and false; // La asignación ocurre antes que el "and". $resultado = true && false; var_dump($resultado); // false — este es el comportamiento esperado ``` Regla práctica: usa siempre `&&` y `||`. Deja `and` / `or` fuera salvo que sepas exactamente por qué los necesitas. ## Operadores de cadena PHP concatena texto con el punto, no con `+`: ```php $nombre = "Angel"; $saludo = "Hola, " . $nombre; // "Hola, Angel" $saludo .= "!"; // "Hola, Angel!" (concatena y asigna) ``` Ojo con la precedencia en PHP 8: desde esa versión, `+` y `-` tienen **mayor** prioridad que `.`. Por eso `echo "Suma: " . 1 + 2;` evalúa `1 + 2` primero. Si mezclas aritmética y concatenación en la misma línea, usa paréntesis: `"Suma: " . (1 + 2)`. ## Operadores de incremento y decremento ```php $n = 5; echo $n++; // 5 (post-incremento: usa el valor y luego suma) echo $n; // 6 echo ++$n; // 7 (pre-incremento: suma y luego usa el valor) echo $n--; // 7 (post-decremento) echo --$n; // 5 ``` La diferencia entre pre y post solo importa cuando usas el valor en la misma expresión. Curiosidad: PHP también incrementa cadenas (`$s = "a"; $s++;` da `"b"`), aunque no las decrementa. ## Operadores bit a bit Operan sobre la representación binaria de los enteros. Se ven poco en el día a día, pero aparecen en permisos, flags y optimizaciones: ```php echo 6 & 3; // 2 (AND bit a bit) echo 6 | 3; // 7 (OR bit a bit) echo 6 ^ 3; // 5 (XOR bit a bit) echo ~6; // -7 (NOT: invierte todos los bits) echo 1 << 3; // 8 (desplaza bits a la izquierda: 1 * 2^3) echo 16 >> 2;// 4 (desplaza a la derecha: 16 / 2^2) ``` ## Operador ternario y null coalescing El ternario condensa un `if-else` en una sola expresión, y el null coalescing maneja valores que pueden no existir: ```php // Ternario completo $mensaje = ($edad >= 18) ? 'Adulto' : 'Menor'; // Ternario corto (operador Elvis): si el valor es truthy lo devuelve, si no, el fallback $nombre = $input ?: 'Invitado'; // Null coalescing: devuelve la izquierda si existe y no es null $usuario = $_GET['user'] ?? 'anónimo'; ``` La diferencia clave entre `?:` y `??`: el Elvis evalúa *truthiness* (un string vacío `""` dispara el fallback), mientras que `??` solo mira si el valor es `null` o no está definido. Es el operador que más tela tiene, así que lo desarrollo en artículos aparte: - **[El operador ternario en PHP](/post/el-operador-ternario-php)** — sintaxis a fondo, operador Elvis y null coalescing. - **[10 ejemplos prácticos del operador ternario](/post/10-ejemplos-operador-ternario-php)** — casos de uso reales: validaciones, clases CSS dinámicas, respuestas de API y más. - **[Null coalescing en PHP (`??` y `??=`)](/post/null-coalescing-php)** — valores por defecto sin warnings y la diferencia con el Elvis. ## El gotcha que cambió en PHP 8: `==` con strings Si vienes de PHP 7, este cambio te puede morder. Al comparar un número con un string **no numérico**, PHP 8 convierte el número a string en lugar de convertir el string a `0`: ```php // PHP 7: true | PHP 8: false var_dump(0 == "foo"); ``` En PHP 7, `"foo"` se convertía a `0` y la comparación daba `true` (una fuente clásica de bugs). PHP 8 lo arregló. Es una razón más para usar `===` siempre que puedas. ## Precedencia: cuando dudes, usa paréntesis PHP tiene una tabla de precedencia larga (qué operador se evalúa antes que cuál). Memorizarla entera no vale la pena. Lo importante: **`**` se evalúa antes que `*` y `/`, que van antes que `+` y `-`; las comparaciones van después; y los lógicos al final.** Cuando una expresión mezcla varios tipos y no estás seguro, los paréntesis dejan la intención explícita y evitan sorpresas. ## Preguntas frecuentes ### ¿Cuál es la diferencia entre `==` y `===` en PHP? `==` compara solo el valor, aplicando conversión de tipos (`5 == "5"` es `true`). `===` exige el mismo valor **y** el mismo tipo (`5 === "5"` es `false`). Usa `===` por defecto: evita bugs de conversión de tipos. ### ¿Qué hace el operador `??` en PHP? El null coalescing devuelve su operando izquierdo si existe y no es `null`; de lo contrario, devuelve el derecho. Sirve para valores por defecto sin generar warnings: `$nombre = $_GET['user'] ?? 'anónimo';`. Desde PHP 7.4 existe `??=` para asignar solo cuando la variable es `null`. ### ¿Qué es el operador `<=>` (spaceship)? Compara dos valores y devuelve un entero menor, igual o mayor que cero según el izquierdo sea menor, igual o mayor que el derecho. Su uso más común es como función de comparación en `usort()` para ordenar arrays. ### ¿Por qué `and` no es lo mismo que `&&`? Hacen la misma operación lógica, pero `and` y `or` tienen menor precedencia que el operador de asignación `=`. Eso provoca resultados inesperados como `$x = true and false;` (que asigna `true` a `$x`). Usa `&&` y `||` para evitarlo. ## Cierre Los operadores son el vocabulario básico de PHP, y casi todos son intuitivos salvo tres puntos: la diferencia entre `==` y `===`, la precedencia de `and`/`or` frente a `=`, y cuándo usar `?:` o `??`. Si tienes esos tres claros, el resto es cuestión de tener la tabla a mano. Para profundizar en el condicional más versátil, sigue con la [guía del operador ternario en PHP](/post/el-operador-ternario-php). --- ### Requisitos de OpenClaw y errores comunes al instalar - URL: https://www.angelcruz.dev/post/requisitos-y-errores-comunes-openclaw - Markdown: https://www.angelcruz.dev/post/requisitos-y-errores-comunes-openclaw.md - Categoría: OpenClaw - Fecha: 2026-06-16 - Excerpt: Los requisitos reales de OpenClaw (Node, RAM, sistema operativo) y cómo resolver los errores más comunes al instalarlo: Node demasiado viejo, el daemon que no arranca, el puerto 18789 y la API key. --- title: "Requisitos de OpenClaw y errores comunes al instalar" excerpt: "Los requisitos reales de OpenClaw (Node, RAM, sistema operativo) y cómo resolver los errores más comunes al instalarlo: Node demasiado viejo, el daemon que no arranca, el puerto 18789 y la API key." date: "2026-06-16" lastModified: "2026-06-16" category: "OpenClaw" tech_article: true seo_title: "Requisitos de OpenClaw + errores comunes al instalar (2026)" seo_description: "Requisitos mínimos de OpenClaw (Node 22.19+/24, 4 GB RAM, macOS/Linux/Windows) y soluciones a los errores comunes al instalar: Node viejo, daemon, puerto 18789 y API key." author: name: "Angel Cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- Antes de instalar [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source) conviene saber qué pide de verdad, y qué hacer cuando algo falla. Este es el resumen de requisitos y los problemas más comunes al instalarlo. Para los pasos por plataforma, está la [guía de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Requisitos mínimos | Requisito | Detalle | | --- | --- | | Node.js | **22.19 o superior** (la documentación recomienda Node 24) | | Sistema operativo | macOS 15+, Linux de 64 bits, o Windows 10 20H2+/11 | | RAM | **4 GB** mínimo; 8 GB para uso intensivo | | Proveedor de IA | Una **API key** (Claude, OpenAI o un modelo local) | El script de instalación oficial resuelve Node por ti, así que en la mayoría de los casos solo necesitas el sistema operativo soportado y la API key. Para correrlo en hardware chico, mira la [guía de OpenClaw en Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi). ## Errores comunes y cómo resolverlos ### 1. El comando falla o pide una versión de Node más nueva Es el problema número uno: tienes una versión de Node anterior a la 22.19. Verifica: ```bash node --version ``` Si es menor, actualiza a Node 24 (con [fnm](https://github.com/Schniz/fnm) o NodeSource) o reinstala con el script oficial, que instala la versión correcta automáticamente. ### 2. El daemon no arranca o OpenClaw no responde tras reiniciar Si registraste el servicio con `--install-daemon` pero el agente no aparece, revisa su estado y sus logs. En Linux: ```bash openclaw status systemctl status openclaw journalctl -u openclaw -f ``` En macOS el daemon corre con launchd. Si quedó mal registrado, vuelve a correr `openclaw onboard --install-daemon`. ### 3. El puerto 18789 está ocupado El gateway escucha en `127.0.0.1:18789`. Si otro proceso ya usa ese puerto, OpenClaw no levanta. Libera el puerto o configura el gateway en otro durante el onboarding. ### 4. No conecta con el modelo de IA Casi siempre es la API key sin configurar o vencida. Vuelve a correr el wizard y carga la clave del proveedor: ```bash openclaw onboard ``` ### 5. En macOS pide permisos de administrador Es esperado: la documentación oficial avisa que *"first run may need an Administrator for Homebrew"*. La primera ejecución puede necesitar permisos de administrador para instalar dependencias con Homebrew. Concédelos esa vez y listo. ### 6. En Windows el script no corre El `install.sh` es un script bash y no corre en PowerShell. En Windows usa el instalador de PowerShell, la app nativa Windows Hub o WSL2: lo explico en la [guía de OpenClaw en Windows](/post/instalar-openclaw-en-windows). ## Preguntas frecuentes ### ¿Cuál es el requisito mínimo de RAM? 4 GB para funcionar; 8 GB recomendados si vas a tener varios canales y skills activos a la vez. ### ¿Node 22 o Node 24? El mínimo es Node 22.19; la documentación recomienda Node 24. Si dejas que el script instale Node, ya elige una versión compatible. ### ¿Funciona sin conexión a internet? OpenClaw el agente corre local, pero la inferencia la hace tu proveedor de IA en la nube (Claude, OpenAI). Sin internet solo funciona si configuraste un modelo local. ## Cierre La mayoría de los problemas al instalar OpenClaw se reducen a tres cosas: una versión de Node vieja, el daemon mal registrado o la API key sin configurar. Con los requisitos claros y estos chequeos, la instalación es directa. Los pasos por plataforma están en la [guía completa de OpenClaw](/post/como-instalar-openclaw-guia-completa). ## Fuente - [Documentación oficial de OpenClaw](https://docs.openclaw.ai/install) --- ### DNS-AID: descubrimiento de agentes de IA a través de DNS - URL: https://www.angelcruz.dev/post/dns-aid-descubrimiento-agentes-ia-dns - Markdown: https://www.angelcruz.dev/post/dns-aid-descubrimiento-agentes-ia-dns.md - Categoría: Inteligencia Artificial - Fecha: 2026-06-14 - Excerpt: DNS-AID es un borrador del IETF para que los agentes de IA se descubran vía DNS, reutilizando registros SVCB (RFC 9460) y DNSSEC. Te explico qué es, cómo funciona y si conviene hoy, leído del draft original. --- title: "DNS-AID: descubrimiento de agentes de IA a través de DNS" excerpt: "DNS-AID es un borrador del IETF para que los agentes de IA se descubran vía DNS, reutilizando registros SVCB (RFC 9460) y DNSSEC. Te explico qué es, cómo funciona y si conviene hoy, leído del draft original." date: "2026-06-14T13:00:00.000Z" category: "Inteligencia Artificial" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" seo_title: "DNS-AID: descubrir agentes de IA por DNS (SVCB + DNSSEC)" seo_description: "Qué es DNS-AID, el borrador del IETF para descubrir agentes de IA vía DNS con registros SVCB (RFC 9460) y DNSSEC. Estructura _agents, flujos de descubrimiento y si conviene hoy." tech_article: article_section: "AI Agents" keywords: "dns-aid, descubrimiento de agentes, svcb, rfc 9460, dnssec, dns for ai discovery" --- ## ¿Qué es DNS-AID? **DNS-AID** (*DNS for AI Discovery*) es una propuesta para que los agentes de IA se descubran entre sí **consultando DNS**, en lugar de pedir un archivo HTTP como `llms.txt`. La idea central: publicar dónde y cómo conectarse a un agente dentro de tu propio espacio de nombres DNS, usando registros **SVCB** ([RFC 9460](https://www.rfc-editor.org/rfc/rfc9460)) bajo una etiqueta `_agents`, firmados con DNSSEC. No inventa nada nuevo a nivel de protocolo: reutiliza tipos de registro que ya existen. Es la pieza que falta en la capa de red de un ecosistema que ya conoces si leíste mi artículo sobre [content negotiation para agentes de IA](/post/content-negotiation-agentes-ia): mientras `llms.txt` y la negociación de contenido operan a nivel HTTP, DNS-AID quiere resolver el descubrimiento **antes** de la primera petición HTTP, en la resolución de nombres. ## Antes de seguir: esto es un borrador, no un estándar Voy a ser directo porque importa: DNS-AID es un **Internet-Draft individual** (`draft-mozleywilliams-dnsop-dnsaid`), escrito por Jim Mozley, Nic Williams, Behcet Sarikaya, Roland Schott y Jeffrey Damick. A junio de 2026 va por la versión **-02**, expira el 28 de noviembre de 2026 y **no está endosado por el IETF**: no tiene grupo de trabajo asignado ni estatus formal en el proceso de estandarización. Reemplaza a un borrador anterior (`draft-mozleywilliams-dnsop-bandaid`). Traducción: es una idea seria y bien escrita, pero todavía especulativa. Puede cambiar de forma radical o quedar en nada. Lo cuento porque entender la propuesta es valioso aunque no la implementes hoy, y porque cualquier blog que te la venda como "el nuevo estándar" no leyó la primera página del draft. ## El problema que intenta resolver Hoy un agente que quiere hablar con un servicio de otra organización tiene que adivinar el endpoint, o depender de un registro centralizado, o rascar HTML. DNS ya resuelve exactamente este tipo de problema para el correo (registros MX) y para servicios genéricos (DNS-SD). DNS-AID aplica el mismo patrón a los agentes: - Es **descentralizado**: cada dominio publica sus propios agentes, sin un registro central que controle todo. - Es **cacheable** y **rápido**: la respuesta viaja por la infraestructura DNS que ya existe en todo el planeta. - Es **verificable**: con DNSSEC, el cliente sabe que los datos no fueron falsificados en el camino. Un principio de diseño que el draft repite: *no* propone cambios a la estructura de los mensajes DNS, ni nuevos códigos de operación, ni nuevos tipos de registro. Solo define **convenciones de nombres** y **perfiles de uso** sobre registros que ya están estandarizados (SVCB, HTTPS, TXT, TLSA). ## La estructura de nombres: el espacio `_agents` DNS-AID usa etiquetas con guion bajo, igual que SPF, DKIM o DNS-SD. El patrón base es: ```txt _._agents. ``` Algunos ejemplos del propio draft: ```txt _agents.example.com zona raíz de descubrimiento de agentes _index._agents.example.com punto de entrada conocido (índice de agentes) _a2a._agents.example.com descubrimiento de servicios agente-a-agente (A2A) _mcp._agents.example.com servicios Model Context Protocol (MCP) billing._mcp._agents.example.org un agente concreto dentro de MCP a4k2f9._mcp._agents.example.org registro por-agente (hoja) ``` La etiqueta `_index` es la clave del descubrimiento: es el "punto de entrada conocido" que un agente consulta cuando sabe el dominio pero no qué tipos de agente ofrece. ## Los registros SVCB (RFC 9460) El mecanismo principal son los registros **SVCB** (*Service Binding*, tipo 64) y su variante **HTTPS** (tipo 65), definidos en la RFC 9460 (Proposed Standard desde 2023). Su gracia es entregarle al cliente **todas las instrucciones de conexión en una sola consulta**: qué protocolo habla el endpoint, en qué puerto, e incluso pistas de IP para ahorrar lookups posteriores. Hay dos modos: - **AliasMode** (prioridad `0`): funciona como un CNAME, pero sirve incluso en el ápex de la zona, donde un CNAME no es legal. - **ServiceMode** (prioridad `1` o mayor): asocia el endpoint con parámetros de conexión. Es el modo que DNS-AID usa para publicar agentes. Ejemplo de registro tal cual aparece en el draft: ```txt _a2a._agents.example.com. 3600 IN SVCB 1 ai-index-svc.example.com. ( alpn="a2a" port=443 ipv4hint=192.0.2.1 ipv6hint=2001:db8::1 ) ``` Lee así: el servicio A2A del dominio vive en `ai-index-svc.example.com`, habla el protocolo `a2a`, en el puerto `443`, y aquí tienes sus IPs para que no tengas que hacer otra consulta. ### Los SvcParams Los parámetros (`SvcParams`) que DNS-AID reutiliza de la RFC 9460: - **`alpn`**: qué protocolo de aplicación soporta el endpoint (`a2a`, `h2`, `h3`...). - **`port`**: el puerto del servicio. - **`ipv4hint` / `ipv6hint`**: pistas de IP que eliminan una consulta A/AAAA de seguimiento. - **`mandatory`**: lista de parámetros que el cliente *tiene* que entender para usar el registro; si no los soporta, debe ignorarlo. ### Parámetros experimentales para metadatos de IA Acá está lo nuevo del draft. Propone claves provisionales para describir capacidades de agente: - **`cap`**: identificador o locator de la capacidad del agente. - **`cap-sha256`**: digest SHA-256 (base64url) para validar la integridad de esa capacidad. - **`policy`**: URI a un bundle de políticas (jurisdicción, manejo de datos). - **`realm`**: token opaco para *scoping* multi-tenant. Como todavía no están registradas ante la IANA, las despliegas con el formato numérico `keyNNNNN`. Un ejemplo completo del draft: ```txt a4k2f9._mcp._agents.example.org. 600 IN SVCB 1 svc-a4k2f9.example.org. alpn="h2" port=443 ipv4hint=192.0.2.5 mandatory=alpn,port,key65001,key65002,key65010 key65001="cap=urn:cap:example:mcp:invoice.v1" key65002="cap-sha256=yvZ0n7q8bE2gYkz8m1j1s0yQG0mC2F6qj3b9pVb6Gk0" key65010="bap=a2a/1,mcp/1" ``` Ese registro anuncia un agente MCP concreto, con la capacidad `invoice.v1`, su hash de integridad y los protocolos que arranca. ## Los cuatro flujos de descubrimiento El draft define cuatro situaciones según cuánto sabe el agente de antemano: 1. **Servicio y dominio conocidos**: consulta SVCB directa. Un solo lookup devuelve todos los parámetros de conexión y los metadatos de capacidad. 2. **Dominio conocido, servicio desconocido**: el agente consulta `_index._agents.example.com` para enumerar qué tipos de agente existen, y luego pide el servicio específico. 3. **Consulta multi-dominio**: el agente pregunta el mismo servicio conocido en varios dominios de confianza en paralelo (por ejemplo `img2txt._a2a._agents.org1.com` y `...org2.com`) y compara para elegir. 4. **Registro consolidado**: cuando no conoce ni el dominio ni el servicio, consulta un registro de terceros (fuera de DNS) que devuelve una lista curada de agentes con ratings y capacidades agregadas. Los tres primeros son DNS puro; el cuarto reconoce que a veces hace falta un directorio externo. ## Seguridad: DNSSEC es obligatorio Esta parte el draft no la deja como opcional. Cito la exigencia: una zona autoritativa pública usada para descubrimiento de agentes **DEBE** usar DNSSEC, y los agentes **DEBEN** usar resolvers validadores. Es más: un resolver **DEBE** tratar las respuestas DNSSEC-bogus como fallos y **NO DEBE** actuar sobre datos de descubrimiento sin firmar o mal firmados. El motivo es obvio: si un atacante puede falsificar tu registro `_a2a._agents`, redirige a los agentes hacia un endpoint malicioso. Sin DNSSEC, todo el modelo de confianza se cae. El draft suma dos registros complementarios: **DANE/TLSA** para atar el certificado del endpoint a la cadena DNSSEC: ```txt _443._tcp.svc-a4k2f9.example.net. 1800 IN TLSA 3 1 1 ( 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789AB ) ``` **Domain Control Validation (DCV)** vía TXT para autorizar enlaces entre servicios: ```txt _agents-challenge 300 IN TXT "bnd-req=svc:crm-sync@vendor.example; nonce=3Qz6l8pA;exp=2025-09-19T06:00:00Z" ``` Un par de notas de rendimiento del draft: usa las pistas de IP para evitar consultas extra, y mantén las respuestas por debajo de ~1232 bytes para esquivar la fragmentación en IPv6. ## ¿Vale la pena implementarlo hoy? Mi opinión honesta, y la razón por la que escribí esto como explicación y no como tutorial de "hazlo ya": **Sáltalo si tienes un sitio de contenido.** Si publicas un blog estático o un sitio corporativo, no tienes ningún servicio A2A ni MCP que descubrir. Publicar `_a2a._agents` sería anunciar una puerta que no existe. Además exige que tu proveedor de DNS soporte registros SVCB y DNSSEC, y un DNSSEC mal configurado puede tumbarte la resolución del dominio entero. Riesgo real, beneficio cero hoy. **Considéralo si expones un agente real.** Si tu producto *es* un servicio de agente (una API A2A, un servidor MCP de cara al público) y quieres que sea descubrible de forma descentralizada y verificable, DNS-AID es la dirección hacia la que apunta esta línea de trabajo. Aun así, trátalo como experimental: implementa, mide, pero no apuestes infraestructura crítica a un borrador que expira en noviembre. **Y si te interesa el estándar**, la mejor inversión ahora mismo no es publicar registros, sino entender SVCB/HTTPS de la RFC 9460, que *sí* es un estándar consolidado y útil por sí solo (HTTP/3, CNAME en el ápex, menos round-trips). ## Cómo se verifica El escáner de [isitagentready.com](https://isitagentready.com) que probablemente te trajo hasta acá comprueba DNS-AID por **DNS-over-HTTPS** (usa el resolver de Cloudflare por defecto, con fallback al de Google) y marca `checks.discoverability.dnsAid.status` como `pass` si encuentra los registros bajo `_agents`. Es decir: el check falla simplemente porque no publicaste esos registros, no porque algo esté roto. ## Conclusión DNS-AID es una idea elegante: llevar el descubrimiento de agentes a la capa donde DNS ya resuelve este problema desde hace décadas, sin inventar tipos de registro nuevos y exigiendo DNSSEC desde el día uno. Pero es un borrador individual, joven y sin respaldo formal del IETF. Vale la pena entenderlo, vale la pena vigilarlo, y vale la pena implementarlo solo si tienes un servicio de agente de verdad y aceptas el carácter experimental. Para un blog, el check de "DNS-AID no encontrado" es ruido: no hay nada que descubrir todavía. --- ## Referencias 1. [draft-mozleywilliams-dnsop-dnsaid — DNS for AI Discovery](https://datatracker.ietf.org/doc/draft-mozleywilliams-dnsop-dnsaid/) — el Internet-Draft original (IETF Datatracker). 2. [RFC 9460 — Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)](https://www.rfc-editor.org/rfc/rfc9460) — el estándar de los registros SVCB/HTTPS. 3. [SKILL.md de DNS-AID en isitagentready.com](https://isitagentready.com/.well-known/agent-skills/dns-aid/SKILL.md) — la guía del escáner que origina la recomendación. --- ### La key fantasma de Cache::flexible() en Laravel - URL: https://www.angelcruz.dev/post/key-fantasma-de-cache-flexible - Markdown: https://www.angelcruz.dev/post/key-fantasma-de-cache-flexible.md - Categoría: Laravel - Fecha: 2026-06-11 - Excerpt: Cache::flexible() guarda una clave interna que nunca escribiste. La encontré construyendo una UI de caché: esta es la historia, el código del framework y cómo la resolví. --- title: "La key fantasma de Cache::flexible() en Laravel" excerpt: "Cache::flexible() guarda una clave interna que nunca escribiste. La encontré construyendo una UI de caché: esta es la historia, el código del framework y cómo la resolví." date: "2026-06-11T22:38:15.000Z" category: "Laravel" seo_title: "La key fantasma de Cache::flexible() en Laravel" seo_description: "Cache::flexible() guarda una clave interna que nunca escribiste. La encontré construyendo una UI de caché: esta es la historia, el código del framework y cómo la resolví." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Hay bugs que rompen la build y se arreglan en cinco minutos. Y hay descubrimientos que no rompen nada (todo sigue "verde") pero que te obligan a abrir el código del framework para entender qué está pasando de verdad. Mantengo un paquete pequeño, [`cache-ui-laravel`](https://github.com/abr4xas/cache-ui-laravel), cuya promesa es simple: **listar, buscar y borrar claves de caché individuales** sin tener que purgar toda la caché. Útil cuando estás depurando en local o limpiando una sola entrada en producción sin tirar abajo todo lo demás. Mientras lo actualizaba a Laravel 13 me topé con una pregunta incómoda: ¿qué pasa con las features nuevas de caché? En concreto, con una que se ha vuelto muy popular: `Cache::flexible()`. ## Primero, ¿qué es `Cache::flexible()`? Es la implementación de Laravel del patrón **stale-while-revalidate** (SWR), el mismo que usan los CDNs y los navegadores. La idea: en lugar de hacer esperar al usuario cuando la caché expira, le sirves el dato "viejo" (stale) inmediatamente y refrescas el valor **en segundo plano**, después de enviar la respuesta. ```php $value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get(); }); ``` Ese `[5, 10]` se lee así: - **Primeros 5 segundos** → el dato está *fresco*. Se devuelve tal cual. - **Entre el segundo 5 y el 10** → el dato está *stale*. Se devuelve igual, pero se registra una *deferred function* que recalcula el valor tras la respuesta. - **A partir del segundo 10** → el dato está *expirado*. Se recalcula en el momento (y sí, ese request paga el coste). Es una herramienta excelente para endpoints muy consultados cuyo cómputo es caro. Pero para poder decidir si un valor está fresco, stale o expirado, Laravel necesita saber **cuándo se creó**. Y ahí está el detalle. ## El descubrimiento Mi paquete tiene un comando `cache:list` que muestra todas las claves. Al probar la compatibilidad con `flexible()`, escribí un test mínimo: ```php it('DEMO: flexible() leaks an internal created key into the listing', function () { Config::set('cache.default', 'file'); Config::set('cache.stores.file.driver', 'key-aware-file'); Cache::store('file')->flexible('users', [5, 10], fn () => 'payload'); $keys = app(CacheUiLaravel::class)->getAllKeys('file'); dump($keys); }); ``` Esperaba ver una sola clave: `users`. Esto es lo que salió: ```bash array:2 [ 0 => "users" 1 => "illuminate:cache:flexible:created:users" ] ``` Una **segunda clave que yo nunca creé**. Aparecía en el listado como si fuera un dato de la aplicación, cuando en realidad es contabilidad interna del framework. ## A las fuentes: qué hace Laravel por debajo La regla de oro al investigar algo así no es buscar en un blog (irónico, lo sé), sino ir al **código original**. Abrí `Illuminate\Cache\Repository` y ahí estaba, sin disimulo: ```php // vendor/laravel/framework/src/Illuminate/Cache/Repository.php const FLEXIBLE_CREATED_KEY_PREFIX = 'illuminate:cache:flexible:created:'; ``` Y el método `flexible()` confirma para qué se usa: ```php public function flexible($key, $ttl, $callback, $lock = null, $alwaysDefer = false) { $key = enum_value($key); [ $key => $value, self::FLEXIBLE_CREATED_KEY_PREFIX.$key => $created, ] = $this->many([$key, self::FLEXIBLE_CREATED_KEY_PREFIX.$key]); if (in_array(null, [$value, $created], true)) { return tap(value($callback), fn ($value) => $this->putMany([ $key => $value, self::FLEXIBLE_CREATED_KEY_PREFIX.$key => Carbon::now()->getTimestamp(), ], $ttl[1])); } // ... } ``` Traducido: **cada vez que guardas un valor con `flexible()`, Laravel guarda DOS entradas**: tu valor, y un timestamp en `illuminate:cache:flexible:created:`. Ese timestamp es lo que le permite calcular después si el dato está fresco o stale. Es una decisión de diseño perfectamente razonable. El problema es que esa segunda clave vive en el **mismo keyspace** que tus datos. Para Redis, para la base de datos o para el sistema de ficheros, no hay diferencia entre `users` y `illuminate:cache:flexible:created:users`: ambas son claves de caché. Y por tanto ambas aparecen cuando listas. > **Apunte interesante:** `flexible()` lee y escribe con `many()` y `putMany()`, no con `get()`/`put()` directos. Eso fue clave para mi paquete, porque tengo un store custom (`key-aware-file`) que envuelve los valores. Como `many()`/`putMany()` delegan internamente en `get()`/`put()`, todo el envoltorio funciona sin tocar nada. La compatibilidad estaba; el único síntoma era el ruido visual. ## La trampa de "no rompe nada" Esto es lo que hace el caso interesante: **nada fallaba**. Los tests pasaban, la caché funcionaba, `flexible()` se comportaba de manual. El único síntoma era cosmético: un listado contaminado con claves internas que ningún humano escribió y que a nadie le importan. Pero los síntomas cosméticos en herramientas de inspección no son inofensivos. Una UI de caché existe precisamente para darte una visión *fiel* de tu keyspace. Si la mitad de lo que muestra es ruido del framework, la herramienta miente un poco. Y una herramienta de depuración que miente es peor que no tener herramienta. ## La solución La corrección fue deliberadamente pequeña. Filtré las claves internas conocidas **en el punto donde agrego el listado**, detrás de una opción de configuración por si alguien quiere verlas: ```php final class CacheUiLaravel { /** * Prefijo que Laravel usa para la entrada companion de Cache::flexible(). * * @see \Illuminate\Cache\Repository::FLEXIBLE_CREATED_KEY_PREFIX */ private const string FLEXIBLE_CREATED_KEY_PREFIX = 'illuminate:cache:flexible:created:'; public function getAllKeys(?string $store = null, ?int $limit = null, int $offset = 0): array { // ... obtención de claves por driver (redis / file / database) ... if (config('cache-ui-laravel.hide_internal_keys', true)) { return $this->filterInternalKeys($keys); } return $keys; } /** * Elimina las claves internas de contabilidad de Laravel del listado. * * @param array $keys * @return array */ private function filterInternalKeys(array $keys): array { return array_values(array_filter( $keys, static fn (string $key): bool => ! str_starts_with($key, self::FLEXIBLE_CREATED_KEY_PREFIX) )); } } ``` Y la opción de configuración, con default sensato: ```php // config/cache-ui-laravel.php 'hide_internal_keys' => env('CACHE_UI_HIDE_INTERNAL_KEYS', true), ``` Tres decisiones de diseño merecen explicación: 1. **El prefijo es una constante, no un literal suelto**, y apunta con `@see` al constante real del framework. Si Laravel lo renombra algún día, el rastro de migas está ahí. 2. **Es configurable.** Por defecto oculto el ruido, pero quien esté depurando el propio `flexible()` puede querer ver las claves internas. Decisión del usuario, no mía. 3. **El `limit` pasa a ser un tope "suave".** Como filtro *después* de pedir las claves al driver, una página de 100 podría devolver 99 visibles. Es un trade-off consciente y documentado: en una herramienta interactiva de búsqueda, vale más un listado honesto que un conteo exacto. ## Lo que decidí NO hacer La primera tentación fue "limpiar bien": cuando borras la clave `users`, borrar también su _companion_ `illuminate:cache:flexible:created:users`. Lo hice... y lo revertí. Por tres razones: - Añadía un `forget()` extra en **cada** borrado, también para claves que nunca pasaron por `flexible()`. - Acoplaba mi `forgetKey()` a un detalle interno del framework. - **Es innecesario.** El "huérfano" se auto-sana: si borras el valor pero queda el timestamp, la siguiente llamada a `flexible()` ve el valor en `null`, entra por la rama de `in_array(null, ...)` y recalcula ambos. Además expira solo por su propio TTL. Y, sobre todo, ya queda oculto del listado por el filtro. > La lección: cuando algo se auto-corrige y es invisible, "arreglarlo" suele ser añadir complejidad para resolver un problema que no existe. ## Conclusiones para llevarte - **`Cache::flexible()` escribe dos entradas por clave.** Si listas, inspeccionas o cuentas claves de caché, ten en cuenta el prefijo `illuminate:cache:flexible:created:`. - **El keyspace de caché es compartido y plano.** Redis, base de datos o ficheros no distinguen entre tus datos y la contabilidad del framework. Cualquier herramienta de inspección tiene que filtrar lo que no es del usuario. - **Cuando algo te sorprenda, lee el framework, no un blog.** El código de `Illuminate` es legible y está a un `grep` de distancia en tu carpeta `vendor/`. La fuente primaria responde preguntas que ningún resumen de tercera mano puede. - **No todo lo que descubres hay que "arreglarlo".** A veces la mejor corrección es la más pequeña, y a veces es ninguna. > Un descubrimiento que no rompía nada terminó mejorando la honestidad de la herramienta y, de paso, me hizo entender mucho mejor cómo funciona una de las features más bonitas de la caché de Laravel. No está mal para una "key fantasma". *¿Usas `Cache::flexible()` en producción? Échale un ojo a tu keyspace con un `SCAN` y busca el prefijo `illuminate:cache:flexible:created:`. Te sorprenderá cuántas hay.* --- ### Composer 2.10: bloqueo de malware y políticas de dependencias - URL: https://www.angelcruz.dev/post/composer-2-10-bloqueo-malware-politicas-dependencias - Markdown: https://www.angelcruz.dev/post/composer-2-10-bloqueo-malware-politicas-dependencias.md - Categoría: PHP - Fecha: 2026-06-01 - Excerpt: Composer 2.10 trae bloqueo de malware nativo, un objeto config.policy unificado para advisories, paquetes abandonados y malware, e inmutabilidad de versiones estables. La respuesta del ecosistema PHP a los ataques de supply chain de 2026. --- title: "Composer 2.10: bloqueo de malware y políticas de dependencias" excerpt: "Composer 2.10 trae bloqueo de malware nativo, un objeto config.policy unificado para advisories, paquetes abandonados y malware, e inmutabilidad de versiones estables. La respuesta del ecosistema PHP a los ataques de supply chain de 2026." date: "2026-06-01T10:00:00.000Z" category: "PHP" seo_title: "Composer 2.10: bloqueo de malware y políticas de dependencias en PHP" seo_description: "Composer 2.10 añade bloqueo de malware nativo (feed de Aikido), el objeto config.policy para malware, advisories y paquetes abandonados, inmutabilidad de versiones y el flag --no-blocking. Qué cambia y qué hacer hoy." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/composer-2-10-bloqueo-malware-politicas-dependencias.png" --- **Composer 2.10 ya está disponible y trae el cambio de seguridad más importante en años para el ecosistema PHP: bloqueo de malware nativo y un sistema unificado de políticas de dependencias.** Composer es el gestor de dependencias estándar de PHP, y esta versión convierte la protección contra ataques de supply chain en comportamiento por defecto de la herramienta, en lugar de dejarla como responsabilidad del desarrollador. No es una mejora cosmética: es la respuesta directa a los ataques de supply chain que golpearon a Packagist durante 2026, como el de `laravel-lang` (22 de mayo de 2026) o el de `intercom/intercom-php` (30 de abril de 2026). En este artículo veo qué cambia realmente, cómo se configura el nuevo objeto `config.policy`, y qué conviene hacer hoy mismo en tus proyectos. ## Por qué llega ahora: los ataques de 2026 El contexto importa, porque explica cada decisión de diseño de esta versión. Según el blog de Packagist, durante 2026 hubo varios ataques de supply chain contra el ecosistema PHP, ejecutados a través de cuentas de GitHub comprometidas y tokens de acceso robados. Dos casos concretos: - **`laravel-lang`** (22 de mayo de 2026): se publicaron versiones maliciosas tras un secuestro de cuenta. - **`intercom/intercom-php`** (30 de abril de 2026): compromiso similar basado en credenciales. El patrón común es el más peligroso de todos: el **re-tagging**. El re-tagging es reescribir una etiqueta (tag) de git existente para cambiar el código al que apunta sin cambiar el número de versión. El atacante no publicaba un paquete nuevo, sino que reescribía una versión que ya era de confianza y que miles de proyectos tenían fijada en su `composer.lock`, inyectándole malware. Una versión que ayer era segura, hoy traía una puerta trasera bajo el mismo número. Composer 2.10 ataca exactamente ese vector. ## Bloqueo de malware nativo La función central de esta versión es la detección de malware integrada, alimentada por el feed de **Aikido** (licencia CC-BY 4.0). Funciona así: > "Flagged versions are removed from the resolution pool, so they cannot be installed via composer update, composer require or composer create-project." En claro: las versiones marcadas como maliciosas **se eliminan del pool de resolución**. No se pueden instalar con `composer update`, `composer require` ni `composer create-project`. Lo más importante es que la protección **también corre durante `composer install`**: > "A malicious release that slipped into a lockfile will not be silently pulled in on CI runs or in production deployments." Esto es clave. Si un release malicioso se coló en tu `composer.lock`, no se va a instalar de forma silenciosa en CI ni en producción. Justo el escenario del re-tagging que describí arriba. ## composer audit ahora detecta malware El comando `composer audit` se actualizó para reportar malware además de las advisories de seguridad de siempre: ```bash # Falla por defecto si encuentra malware en el árbol de dependencias composer audit ``` > "The same versions are surfaced by composer audit, which fails the audit when finding malware by default." Junto con esto cambió el comportamiento de los exit codes (un detalle a tener en cuenta en CI): > "composer audit exit codes now use `0` for success and `1` when the audit fails." Si tienes un step de CI que corre `composer audit`, revisa que estés interpretando bien el código de salida: `0` es éxito, `1` es fallo. ## config.policy: un solo objeto para todas las políticas Aquí está el cambio estructural. Composer 2.10 introduce el objeto **`config.policy`**, que reemplaza la configuración anterior bajo `config.audit` y unifica tres políticas integradas bajo una misma estructura: | Política | Qué cubre | |----------|-----------| | `malware` | Versiones marcadas como maliciosas | | `advisories` | Versiones con vulnerabilidades de seguridad conocidas | | `abandoned` | Paquetes marcados como abandonados | Cada política comparte la misma estructura, con tres opciones: `block`, `audit` e `ignore`. Y cada una llega con defaults sensatos: | Política | Update | Audit | Install | |----------|--------|-------|---------| | **malware** | bloqueado | falla | bloqueado | | **advisories** | bloqueado | falla | instalable (para evaluación) | | **abandoned** | solo lo reporta el audit | reporta | no bloqueado | La lógica detrás de los defaults es razonable: el malware se bloquea en todos lados sin excepción; una advisory te frena en el update y el audit, pero te deja instalar la versión si necesitas evaluarla; y un paquete abandonado solo se reporta, porque "abandonado" no significa "inseguro". ### Políticas personalizadas Además de las tres integradas, puedes definir tus propias políticas apuntando a una fuente HTTPS. Por ejemplo, una lista interna de paquetes vetados por tu organización: ```json { "config": { "policy": { "company-policy": { "sources": [ { "type": "url", "url": "https://example.com/bad-pkgs.json" } ], "audit": "fail" } } } } ``` Esto abre la puerta a políticas corporativas centralizadas sin depender de herramientas externas. ### Saltarse el bloqueo cuando hace falta Si necesitas instalar algo bloqueado de forma puntual (por ejemplo, para reproducir o analizar un problema), existe el flag `--no-blocking`: ```bash composer install --no-blocking ``` También funciona como variable de entorno, útil en entornos donde no controlas el comando directamente: ```bash COMPOSER_NO_BLOCKING=1 composer install ``` Mi recomendación: úsalo solo de forma local y consciente, nunca en CI ni en producción. El bloqueo está ahí por una razón. ## Inmutabilidad de versiones estables Esta es la pieza que cierra el círculo contra el re-tagging. A partir de ahora, **las versiones etiquetadas como estables no pueden reescribirse silenciosamente** mediante un re-tag en git. Una versión publicada queda fija: si el contenido de un tag cambia, deja de ser válido en lugar de instalarse como si nada. Combinado con el bloqueo durante `install`, esto significa que el escenario de `laravel-lang` (reescribir una versión que ya estaba instalada en miles de `composer.lock`) deja de ser viable de forma silenciosa. ## Deprecación del source fallback Composer 2.10 deprecó el comportamiento de **source fallback**, por el riesgo de seguridad que implica cuando la descarga de un artefacto falla en repositorios privados. Hay una nueva opción `source-fallback` para reactivar el comportamiento legacy si de verdad lo necesitas, pero **se elimina por completo en Composer 2.11**. Conviene ir migrando ya. ## Otras mejoras de la versión No todo es seguridad. La 2.10 trae también un par de mejoras de ergonomía que vale la pena conocer: **Wildcards en `--with`.** Ahora puedes restringir varias dependencias de un namespace en una sola pasada: ```bash composer update --with "acme/*:^2.0" ``` **`--require` en `create-project`.** Puedes añadir dependencias extra al crear un proyecto: ```bash composer create-project acme/skeleton my-project --require="acme/extra-bundle:^1.0" ``` **`--bump-after-update` más preciso.** Ahora solo hace bump de los paquetes que realmente se actualizaron, no de todo el `composer.json`. A esto se suman optimizaciones en el autoloading de plugins y una reducción del uso de memoria del `PoolOptimizer`. ## Lo que viene (roadmap de Packagist) El equipo de Packagist dejó claro que la 2.10 es un paso dentro de un plan más grande. Esto todavía no está disponible, pero está anunciado: - **Política de antigüedad mínima (cooldown):** rechazar la instalación de versiones publicadas hace muy poco, para dar margen a que se detecte malware antes de que llegue a producción. - **MFA obligatorio** en Packagist.org, con el estado de MFA visible en los perfiles de maintainer y registrado en el transparency log. - **Organizational Package Ownership**, para reemplazar las cuentas compartidas (uno de los grandes vectores de los ataques de 2026). - **Flujo de release escalonado con FIDO2** para paquetes con base de usuarios grande. - **Hosting directo de artefactos** con provenance SLSA y attestations de Sigstore, alineándose con OpenSSF Level 3 y SLSA L3-L4. Es una hoja de ruta seria. Vale la pena seguirla si mantienes paquetes propios. ## Qué hacer hoy Sin teoría, lo accionable: 1. **Actualiza Composer a 2.10**: `composer self-update`. 2. **Corre `composer audit`** en tus proyectos y en tu pipeline de CI. Revisa que el step interprete bien los exit codes (`0` éxito, `1` fallo). 3. **Si mantienes paquetes en Packagist, habilita MFA ahora.** La propia gente de Packagist lo pide de forma explícita: *"If you maintain any package on Packagist.org and don't have MFA enabled, please enable it now."* 4. **Confía en los defaults de `config.policy`.** Están bien pensados; solo toca la configuración si tienes una necesidad real. 5. **Commitea tu `composer.lock`.** Sigue siendo tu primera línea de reproducibilidad, y ahora el bloqueo durante `install` lo respalda. Si todavía tienes dudas sobre por qué, lo expliqué en detalle en [La importancia del archivo composer.lock en PHP](/post/importancia-composer-lock-php). ## Preguntas frecuentes ### ¿Cómo actualizo a Composer 2.10? Con `composer self-update`. Si lo instalaste vía package manager del sistema (apt, brew, etc.), actualízalo por esa misma vía. ### ¿El bloqueo de malware me puede romper un deploy en producción? Puede frenar un `composer install` si detecta una versión marcada como maliciosa en tu `composer.lock`, y eso es justamente lo que quieres. No bloquea instalaciones legítimas: solo las versiones que están en el feed de malware. Si te frena, es señal de que tenías un release comprometido fijado. ### ¿De dónde salen los datos de malware? Del feed de **Aikido** (licencia CC-BY 4.0), integrado en los metadatos de Packagist.org desde marzo de 2026. Packagist dejó abierta la puerta a sumar más proveedores con licencias libres adecuadas. ### ¿`config.policy` reemplaza a `config.audit`? Sí. El objeto `config.policy` consolida bajo una misma estructura lo que antes estaba en `config.audit`, y suma las políticas de malware y paquetes abandonados junto con las advisories. ### ¿Puedo desactivar el bloqueo temporalmente? Sí, con el flag `--no-blocking` o la variable de entorno `COMPOSER_NO_BLOCKING`. Úsalo solo de forma local y puntual, nunca en CI ni en producción. ### ¿Qué es el re-tagging y por qué importa tanto? Es reescribir una etiqueta (tag) de git existente para cambiar el código que apunta sin cambiar el número de versión. Fue el vector de los ataques de 2026: una versión que ya estaba instalada en miles de proyectos se reescribía con malware. La inmutabilidad de versiones estables de Composer 2.10 corta ese vector. ## En resumen | Cambio | Estado | |--------|--------| | Bloqueo de malware en update/require/create-project | Nuevo | | Bloqueo de malware durante `install` (protege el lockfile) | Nuevo | | `composer audit` detecta malware | Nuevo | | Exit codes de `composer audit` (`0`/`1`) | Cambio de comportamiento | | Objeto `config.policy` unificado | Nuevo (reemplaza `config.audit`) | | Políticas personalizadas vía HTTPS | Nuevo | | Flag `--no-blocking` / `COMPOSER_NO_BLOCKING` | Nuevo | | Inmutabilidad de versiones estables | Nuevo | | Deprecación de source fallback | Deprecado (se elimina en 2.11) | | Wildcards en `--with` | Nuevo | | `--require` en `create-project` | Nuevo | Composer 2.10 no es una versión más. Es el momento en el que el ecosistema PHP movió la seguridad de supply chain de "responsabilidad del desarrollador" a "comportamiento por defecto de la herramienta". Actualiza, corre `composer audit`, y si mantienes paquetes, habilita MFA hoy. ## Fuentes - [Malware Blocking and Dependency Policies in Composer 2.10 (Laravel News)](https://laravel-news.com/malware-blocking-and-dependency-policies-in-composer-210) - [Composer 2.10 Release (Packagist Blog)](https://blog.packagist.com/composer-2-10-release/) - [An Update on Composer & Packagist Supply Chain Security (Packagist Blog)](https://blog.packagist.com/an-update-on-composer-packagist-supply-chain-security/) - [Aikido malware feed (CC-BY 4.0)](https://www.aikido.dev/) --- ### Cómo crear un plugin para Claude Cowork (y Claude Code) a partir de tus skills - URL: https://www.angelcruz.dev/post/crear-plugin-claude-cowork-claude-code-desde-skills - Markdown: https://www.angelcruz.dev/post/crear-plugin-claude-cowork-claude-code-desde-skills.md - Categoría: Inteligencia Artificial - Fecha: 2026-05-31 - Excerpt: Guía práctica para empaquetar tus skills en un plugin que funciona igual en Claude Cowork y Claude Code, con el truco de OAuth para conectar un MCP remoto sin API key. --- title: "Cómo crear un plugin para Claude Cowork (y Claude Code) a partir de tus skills" excerpt: "Guía práctica para empaquetar tus skills en un plugin que funciona igual en Claude Cowork y Claude Code, con el truco de OAuth para conectar un MCP remoto sin API key." date: "2026-05-31T12:00:00.000Z" category: "Inteligencia Artificial" tech_article: true author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/og-claude-cowork.jpg" seo_title: "Crear un plugin de Claude Cowork y Claude Code desde tus skills" seo_description: "Empaqueta tus skills en un plugin para Claude Cowork y Claude Code: manifest, .mcp.json y marketplace. Incluye el truco de OAuth para conectar un MCP remoto sin API key." --- Si ya tienes skills que usas a diario en Claude Code, empaquetarlas en un plugin es el paso que las vuelve compartibles, versionables y reutilizables entre proyectos. Y el mismo plugin funciona en Claude Cowork y en Claude Code, sin cambios. ## Claude Cowork y Claude Code usan el mismo formato Esto no es una suposición. El repositorio oficial de plugins para Claude Cowork lo dice de forma literal: > "Built for Claude Cowork, also compatible with Claude Code." > > "Both platforms use the same plugin structure, enabling shared compatibility between the two products." Es decir: **construyes el plugin una vez y corre en los dos productos**. La estructura de directorios, el manifest y la forma de declarar componentes son idénticos. La única diferencia es la distribución (en Cowork los plugins se instalan desde el portal en `claude.com/plugins/`), no cómo armas el paquete. Por eso el resto del artículo se apoya en la referencia de Claude Code: es la misma especificación. ## La estructura de un plugin Un plugin es un directorio con un manifest y, opcionalmente, carpetas para cada tipo de componente: ```text mi-plugin/ ├── .claude-plugin/ │ └── plugin.json # El manifest (lo único que va aquí dentro) ├── skills/ # Skills: /SKILL.md, Claude las usa automáticamente ├── commands/ # Skills como archivos .md planos (formato heredado) ├── agents/ # Subagentes ├── hooks/ # hooks.json con manejadores de eventos └── .mcp.json # Servidores MCP, en la RAÍZ del plugin ``` Hay un error que la propia doc marca como el más común: > "Common mistake: Don't put `commands/`, `agents/`, `skills/`, or `hooks/` inside the `.claude-plugin/` directory. Only `plugin.json` goes inside `.claude-plugin/`. All other directories must be at the plugin root level." **La regla:** dentro de `.claude-plugin/` va solo el `plugin.json`. Todo lo demás, incluido `.mcp.json`, vive en la raíz del plugin, un nivel más arriba. Si pones `skills/` dentro de `.claude-plugin/`, el plugin carga pero tus skills no aparecen. ## El manifest: `plugin.json` El manifest describe la identidad del plugin. Lo bueno es que casi todo es opcional: > "If you include a manifest, `name` is the only required field." Un manifest mínimo válido es literalmente esto: ```json { "name": "mi-plugin" } ``` El `name` cumple doble función: identifica el plugin y sirve de **namespace** para sus componentes. Una skill `review` dentro de un plugin llamado `mi-plugin` se invoca como `/mi-plugin:review`. Ese namespacing es lo que evita choques cuando tienes varios plugins con skills del mismo nombre. En la práctica vas a querer agregar metadatos. El schema completo soporta, entre otros campos: ```json { "name": "mi-plugin", "displayName": "Mi Plugin", "version": "1.0.0", "description": "Qué hace el plugin, en una línea", "author": { "name": "Tu Nombre", "email": "tu@correo.com", "url": "https://github.com/tu-usuario" }, "homepage": "https://tu-sitio.com/mi-plugin", "repository": "https://github.com/tu-usuario/mi-plugin", "license": "MIT", "keywords": ["skills", "automatizacion"] } ``` Un detalle sobre `version` que conviene entender, porque cambia cómo reciben las actualizaciones tus usuarios: > "If set, users only receive updates when you bump this field. If omitted and your plugin is distributed via git, the commit SHA is used and every commit counts as a new version." **En claro:** si fijas `version`, controlas tú el ritmo de releases. Si la omites y distribuyes por git, cada commit cuenta como versión nueva. Para algo que cambia seguido, fijar la versión te ahorra ruido. ## El truco que importa: apuntar a skills que ya tienes, sin moverlas Este es el punto que conecta el plugin con lo que ya tienes en tu repo. El manifest acepta **rutas custom** para cada tipo de componente. El campo `skills` está documentado así: > `skills` (tipo `string | array`): "Custom skill directories containing `/SKILL.md` (in addition to default `skills/`)" Traducido: **no tienes que mover tus skills a la carpeta `skills/` del plugin**. Puedes dejarlas donde viven y apuntar el manifest hacia ellas: ```json { "name": "mi-plugin", "skills": "./ruta/a/mis/skills/" } ``` Lo mismo aplica para `commands`, `agents`, `hooks` y `mcpServers`: todos aceptan rutas o arrays de rutas. Así un repo que ya tiene una carpeta de skills se convierte en plugin con un `plugin.json` que apenas las referencia. ### De dónde sale el nombre con el que invocas una skill Hay una sutileza que conviene precisar para no llevarte una sorpresa: **el nombre que tecleas para invocar la skill viene de dónde vive el archivo, no del frontmatter**, salvo un caso. La doc lo dice así: > "The command you type to invoke a skill comes from where the skill file lives. The frontmatter `name` field sets the display label shown in skill listings and, except for a plugin-root `SKILL.md`, does not change what you type after `/`." En concreto: | Ubicación del `SKILL.md` | Qué determina el comando | | :--- | :--- | | Subdirectorio `skills/` del plugin | El **nombre del directorio**, con el namespace del plugin: `mi-plugin/skills/review/SKILL.md` da `/mi-plugin:review` | | `SKILL.md` en la raíz del plugin | El campo `name` del frontmatter, con el nombre del directorio del plugin como fallback | O sea: en el caso normal (skills dentro de `skills/`), manda el **nombre de la carpeta**. El `name` del frontmatter solo fija el comando cuando el `SKILL.md` está en la raíz del plugin, porque ahí no hay carpeta de donde tomarlo. El `description` del frontmatter, eso sí, siempre importa: es lo que Claude lee para decidir cuándo cargar la skill automáticamente. ## Probar el plugin en local antes de publicar No necesitas publicar nada para probar. La doc da dos herramientas: ```bash # Cargar el plugin directamente, sin instalarlo claude --plugin-dir ./mi-plugin # Validar la estructura y el manifest claude plugin validate ./mi-plugin ``` Mientras desarrollas, `/reload-plugins` recarga skills, agentes, hooks y servidores MCP del plugin sin reiniciar la sesión. Y antes de publicar, vale correr la validación con `--strict`, que convierte los warnings (por ejemplo, un campo mal escrito en el manifest) en errores: ```bash claude plugin validate ./mi-plugin --strict ``` ## El punto clave: conectar un MCP remoto con OAuth, sin API key Un plugin puede traer servidores MCP que se conectan solos cuando el plugin está activo. Se declaran en `.mcp.json`, en la raíz del plugin. Para un servidor remoto el formato es mínimo: ```json { "mcpServers": { "thatseoagent": { "type": "http", "url": "https://thatseoagent.com/api/mcp" } } } ``` Fíjate en lo que **no** está: no hay header `Authorization`, no hay API key, no hay secreto. Y es a propósito. ### Por qué omitir el header dispara OAuth Cuando Claude Code intenta usar un servidor remoto, lo marca como "necesita autenticación" si el servidor responde `401 Unauthorized` o `403 Forbidden`: > "Claude Code marks a remote server as needing authentication when the server responds with `401 Unauthorized` or `403 Forbidden`. ... A custom server that returns a `WWW-Authenticate` header pointing to its authorization server gets the same automatic discovery as any other remote server." A partir de ese 401, Claude Code descubre los endpoints de OAuth por el camino estándar: > "By default, Claude Code first checks RFC 9728 Protected Resource Metadata at `/.well-known/oauth-protected-resource`, then falls back to RFC 8414 authorization server metadata at `/.well-known/oauth-authorization-server`." Si el authorization server soporta **Dynamic Client Registration** (DCR), Claude Code se registra como cliente OAuth solo, sin que tú ni el usuario tengan que crear una app a mano. Lo sabemos por el contrario: la doc dice que cuando el server *no* soporta DCR aparece el error "Incompatible auth server: does not support dynamic client registration" y hay que cargar credenciales manualmente. Si DCR está disponible, ese paso desaparece. **El resultado:** el usuario instala el plugin, la primera vez que se usa la herramienta se abre el navegador, hace login, y listo. Cero API key, cero copiar tokens. ### La verificación contra el endpoint real Esto no es teoría. Lo confirmé inspeccionando los `.well-known` del propio servidor MCP de That SEO Agent. El primero, el protected-resource metadata: ```bash curl https://thatseoagent.com/.well-known/oauth-protected-resource ``` ```json { "resource": "https://thatseoagent.com/api/mcp", "authorization_servers": ["https://thatseoagent.com"], "scopes_supported": ["mcp"], "bearer_methods_supported": ["header"] } ``` Ese `authorization_servers` apunta a dónde buscar el segundo documento, el del authorization server: ```bash curl https://thatseoagent.com/.well-known/oauth-authorization-server ``` ```json { "issuer": "https://thatseoagent.com", "authorization_endpoint": "https://thatseoagent.com/oauth/authorize", "token_endpoint": "https://thatseoagent.com/oauth/token", "registration_endpoint": "https://thatseoagent.com/oauth/register", "response_types_supported": ["code"], "code_challenge_methods_supported": ["S256"], "scopes_supported": ["mcp"] } ``` Las dos claves que hacen funcionar todo el flujo sin configuración: - **`registration_endpoint` presente**: el server soporta DCR, así que Claude Code se registra solo. - **`code_challenge_methods_supported: ["S256"]`**: hay PKCE, el flujo de autorización es seguro para un cliente público. El server-card (`/.well-known/mcp/server-card.json`) lo cierra: transporte `streamable-http` en `https://thatseoagent.com/api/mcp` y `authentication.type` igual a `oauth2`. Todo encaja con lo que describe la doc. ### La trampa: un header inválido NO cae a OAuth Este es el detalle que rompe a mucha gente, y la razón para omitir el header en lugar de poner uno "por las dudas". Si configuras un `Authorization` inválido, el flujo OAuth no se activa como fallback: > "If you configured `headers.Authorization` for the server and the server rejects that header, Claude Code reports the connection as failed instead of falling back to OAuth. Check that the token is valid for the MCP endpoint, or remove the header to use the OAuth flow." La lectura es directa: para que OAuth se dispare solo, **no pongas el header**. Un header presente pero rechazado se interpreta como "el usuario quiso autenticarse con token y falló", no como "probemos OAuth". Distinto es si tu MCP usa una **API key estática** y no OAuth. Ahí el patrón correcto es otro, con el header y expansión de variables de entorno para no hardcodear el secreto: ```json { "mcpServers": { "mi-api": { "type": "http", "url": "https://api.example.com/mcp", "headers": { "Authorization": "Bearer ${MI_API_KEY}" } } } } ``` Pero si el server soporta OAuth con DCR, como el del ejemplo, el camino sin header es más limpio para quien instala el plugin. ## Distribuir: el `marketplace.json` Para que otros instalen tu plugin con un par de comandos, necesitas un marketplace: un catálogo `marketplace.json` que vive en `.claude-plugin/marketplace.json` dentro de tu repo. Los campos requeridos son `name`, `owner` y `plugins`: ```json { "name": "mis-plugins", "owner": { "name": "Tu Nombre" }, "plugins": [ { "name": "mi-plugin", "source": "./plugins/mi-plugin", "description": "Qué hace el plugin" } ] } ``` Cada entrada en `plugins[]` necesita como mínimo `name` y `source`. El `source` dice de dónde sacar ese plugin. Para uno que vive en el mismo repo, se usa una ruta relativa, y la doc es precisa sobre la regla: > Relative path: "Local directory within the marketplace repo. Must start with `./`. Resolved relative to the marketplace root, not the `.claude-plugin/` directory." ### Auto-hospedar cuando el plugin ES el repo Esto es recomendación de diseño, derivada de esa regla. Si tu repositorio **es** un único plugin (su `plugin.json` está en `.claude-plugin/plugin.json`, en la raíz), puedes poner el `marketplace.json` al lado y apuntar el `source` a la raíz misma: ```json { "name": "mis-plugins", "owner": { "name": "Tu Nombre" }, "plugins": [ { "name": "mi-plugin", "source": "./", "description": "El plugin es el repo entero" } ] } ``` Como el `source` se resuelve desde la raíz del marketplace (el directorio que contiene `.claude-plugin/`), `"./"` apunta al repo completo, que es justo donde está el plugin. Es la forma más simple de auto-hospedar: un repo, un plugin, un marketplace, sin subdirectorios. Los ejemplos explícitos de la doc usan rutas como `./plugins/mi-plugin`; el `"./"` es la aplicación natural de esa regla a un repo de un solo plugin. ## Instalación para quien usa tu plugin Con el marketplace en un repo de GitHub, cualquiera lo instala en dos pasos: ```bash # 1. Registrar tu marketplace (atajo owner/repo de GitHub) /plugin marketplace add tu-usuario/tu-repo # 2. Instalar el plugin desde ese marketplace /plugin install mi-plugin@mis-plugins ``` El formato `@` no es decorativo: el `marketplace` es el `name` que pusiste en tu `marketplace.json`, no el nombre del repo. A partir de ahí, las skills quedan disponibles como `/mi-plugin:nombre-skill` y, si incluiste un `.mcp.json` con un server OAuth, la primera vez que se use se abrirá el navegador para autenticar. ## En resumen 1. Junta tus skills (no hace falta moverlas: el manifest apunta a ellas con el campo `skills`). 2. Crea `.claude-plugin/plugin.json` con al menos `name`. 3. Si vas a traer un MCP remoto con OAuth, agrega `.mcp.json` en la raíz **sin** header de autorización. 4. Prueba en local con `claude --plugin-dir ./mi-plugin` y valida con `claude plugin validate --strict`. 5. Publica con un `marketplace.json` y comparte los dos comandos de instalación. Lo construyes una vez y corre en Claude Cowork y en Claude Code. Y si tu MCP expone OAuth con DCR, quien lo instala no tiene que ver una sola API key. ## Preguntas Frecuentes ### ¿Un plugin de Claude Code funciona igual en Claude Cowork? Sí. El repositorio oficial de plugins para Cowork lo confirma: **ambas plataformas usan la misma estructura de plugin**. Construyes el paquete una vez y corre en los dos productos; lo único que cambia es la vía de distribución. ### ¿Tengo que mover mis skills a la carpeta del plugin? No. El campo `skills` del manifest acepta **rutas custom**, así que puedes dejar tus skills donde ya viven y apuntar el `plugin.json` hacia esa carpeta. No hace falta reorganizar tu repo. ### ¿Necesito publicar un marketplace para probar el plugin? No. Cárgalo directo con `claude --plugin-dir ./mi-plugin`, sin instalar nada. Usa `/reload-plugins` para recargar cambios sin reiniciar la sesión, y `claude plugin validate` para revisar el manifest. ### ¿Cómo conecto un MCP con OAuth sin pedirle una API key al usuario? Declara el server en `.mcp.json` **sin** header `Authorization`. Si el servidor responde `401` y expone `/.well-known/oauth-protected-resource` con un authorization server que soporta Dynamic Client Registration, Claude Code se registra solo y abre el navegador para el login. Cero API key. ### ¿Por qué mi MCP falla en vez de abrir el navegador para OAuth? Casi siempre porque dejaste un header `Authorization` inválido. La doc es clara: si configuras ese header y el servidor lo rechaza, Claude Code **reporta la conexión como fallida en lugar de caer a OAuth**. Quita el header para que se dispare el flujo del navegador. ### ¿Qué pasa si no defino el campo `version` en el manifest? Si lo omites y distribuyes por git, Claude Code usa el commit SHA y **cada commit cuenta como una versión nueva**. Si fijas `version`, tus usuarios solo reciben actualizaciones cuando subes ese número. ### Instalé el plugin pero mi skill no aparece, ¿qué reviso? Lo primero: que la carpeta `skills/` esté en la **raíz del plugin** y no dentro de `.claude-plugin/`, que es el error más común. Dentro de `.claude-plugin/` va solo el `plugin.json`. Si la estructura está bien, corre `/reload-plugins`. ### ¿El nombre con el que invoco la skill sale del frontmatter o del archivo? Del **nombre del directorio** de la skill, con el namespace del plugin (`mi-plugin/skills/review/SKILL.md` da `/mi-plugin:review`). El `name` del frontmatter solo fija el comando cuando el `SKILL.md` está en la raíz del plugin. **Última actualización**: 31 de mayo de 2026. --- ## Fuentes - [Create plugins (code.claude.com)](https://code.claude.com/docs/en/plugins) - [Plugins reference (code.claude.com)](https://code.claude.com/docs/en/plugins-reference) - [Create and distribute a plugin marketplace (code.claude.com)](https://code.claude.com/docs/en/plugin-marketplaces) - [Connect Claude Code to tools via MCP (code.claude.com)](https://code.claude.com/docs/en/mcp) - [Extend Claude with skills (code.claude.com)](https://code.claude.com/docs/en/skills) - [anthropics/knowledge-work-plugins (GitHub)](https://github.com/anthropics/knowledge-work-plugins) - [oauth-protected-resource de That SEO Agent (.well-known)](https://thatseoagent.com/.well-known/oauth-protected-resource) - [oauth-authorization-server de That SEO Agent (.well-known)](https://thatseoagent.com/.well-known/oauth-authorization-server) - [server-card del MCP de That SEO Agent (.well-known)](https://thatseoagent.com/.well-known/mcp/server-card.json) --- ### Partículas atmosféricas en Next.js sin Canvas: performante con CSS puro - URL: https://www.angelcruz.dev/post/particulas-atmosfericas-nextjs-css-sin-canvas - Markdown: https://www.angelcruz.dev/post/particulas-atmosfericas-nextjs-css-sin-canvas.md - Categoría: Next.js - Fecha: 2026-05-24 - Excerpt: Cómo construir un sistema de partículas (embers, chispas, copos de nieve) en Next.js con CSS puro: sin Canvas, sin requestAnimationFrame, SSR-safe, configurable por palette y dirección, y con cero impacto en performance. --- title: "Partículas atmosféricas en Next.js sin Canvas: performante con CSS puro" excerpt: "Cómo construir un sistema de partículas (embers, chispas, copos de nieve) en Next.js con CSS puro: sin Canvas, sin requestAnimationFrame, SSR-safe, configurable por palette y dirección, y con cero impacto en performance." date: "2026-05-24T12:00:00.000Z" category: "Next.js" seo_title: "Partículas CSS-only en Next.js: sistema de embers sin canvas ni rAF" seo_description: "Construye un efecto de partículas atmosféricas en Next.js usando CSS puro: keyframes con cqh, generación determinista para SSR, palettes configurables y respeto por prefers-reduced-motion. Sin canvas, sin loops JS." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/4/01KF4MJMCWDRH9M8H7J0P25GP9.png" --- Hace unas semanas trabajaba en una ficha de personaje para una campaña de D&D y quería que el avatar tuviera atmósfera: chispas de forja flotando alrededor, como si saliera directo del taller del herrero. La imagen del avatar venía con el fondo removido (transparente), así que necesitaba una capa visual detrás para darle contexto. La primera reacción de cualquier desarrollador es: **Canvas + `requestAnimationFrame`**. Es la solución por defecto para sistemas de partículas. Y es exactamente la que **descarté** después de pensarlo bien. En este artículo te muestro cómo construir un sistema de partículas atmosféricas usando **solo CSS**: sin Canvas, sin loops de JavaScript en runtime, SSR-safe, configurable por palette de colores y dirección de movimiento. El resultado es performante, accesible, y se compone limpio con otros elementos de la página. ## ¿Por qué CSS y no Canvas? Cuando empecé a planificar el efecto, archivé la idea de Canvas en un documento de decisiones. Las razones que tuve: **1. Costo de mantenimiento permanente.** Un `requestAnimationFrame` corre cada frame mientras el componente está montado. Aunque pauses cuando la pestaña está oculta o cuando el elemento está fuera del viewport (con IntersectionObserver), sigues pagando ese costo en cada frame visible. Para un avatar de 360px que probablemente esté en pantalla mientras el usuario lee el resto del contenido, eso son ~60 frames por segundo de cálculos de física más reflows. **2. Riesgo de "AI-slop estético".** Las partículas ambient sobre fondos oscuros son un cliché de landings de SaaS futuristas. Si no se ejecuta perfectamente, lee como template genérico en lugar de diseño intencional. El criterio mental: si alguien puede mirar el efecto y decir "esto lo hizo AI" sin dudarlo, fallaste. Las partículas Canvas mal calibradas caen en esa categoría rápido. **3. SSR y hidratación.** Canvas necesita JavaScript en el cliente para arrancar. Eso significa que durante el primer paint el avatar no tiene ningún efecto, y al hidratarse aparece la animación de golpe. Hay un flash visual que rompe la continuidad. **4. Para 10-20 partículas, CSS es más performante que Canvas.** Esto sorprende a la mayoría. El navegador composita transforms en GPU; un Canvas requiere repintar cada frame en CPU y luego transferirlo. Para conteos pequeños, la balanza se inclina hacia CSS. **5. CSS sobrevive `prefers-reduced-motion` con un media query.** En Canvas, tienes que escribir lógica JS para detectar la preferencia y apagar el loop. En CSS es una sola regla. La conclusión: **Canvas tiene sentido para sistemas de partículas con física compleja, miles de partículas, o efectos que dependen de interacción mouse/touch**. Para "partícula flotando hacia arriba con un fade al final", CSS es la herramienta correcta. ## La arquitectura del sistema Para mantenerlo reutilizable, conviene separar la lógica en tres piezas: 1. **Un módulo de lógica pura** que genere los datos de las partículas (posiciones, tamaños, duraciones, hues). Esto es testeable y reutilizable. 2. **Un componente atómico** que renderice una capa de partículas (las anima, aplica la palette, controla la dirección). Es el primitivo del sistema. 3. **Composiciones específicas** que usen ese componente atómico para casos concretos (un avatar con partículas detrás y delante, un hero con partículas cayendo, etc.). Esta separación es importante: el componente de partículas sirve para cualquier contexto, y las composiciones construyen patrones específicos sin tocar la lógica de animación. ## Pieza 1: generación determinista de partículas La pregunta clave para SSR: ¿cómo generar posiciones aleatorias para las partículas sin que el server y el cliente computen valores diferentes? Si usas `Math.random()`, el server genera unos valores, el cliente genera otros, React detecta el mismatch durante la hidratación y dispara un warning (o peor: re-renderiza el árbol entero). Inaceptable. La solución: un **LCG (Linear Congruential Generator)** determinista. Misma seed, misma secuencia, siempre. ```typescript export function seededRandom(seed: number): () => number { let state = seed; return () => { state = (state * 1664525 + 1013904223) % 4294967296; return state / 4294967296; }; } ``` Los números `1664525` y `1013904223` son las constantes del Numerical Recipes LCG: no son arbitrarias, están elegidas para tener buen período. El módulo `4294967296` (2³²) acota el state a 32 bits sin overflow en JS. Con esto, server y cliente computan **exactamente la misma secuencia de partículas**. Cero hydration mismatch. Ahora la generación del array de partículas: ```typescript interface Particle { left: number; // % horizontal donde empieza size: number; // px de diámetro duration: number; // s de animación delay: number; // s antes de arrancar hue: number; // OKLCH hue } interface Palette { hot: number; // hue principal cool: number; // hue secundario accent: number; // hue de acento } export function buildParticles( count: number, baseDuration: number, palette: Palette, seed: number = 42 ): Particle[] { const rand = seededRandom(seed); const particles: Particle[] = []; for (let i = 0; i < count; i++) { const r1 = rand(); const r2 = rand(); const r3 = rand(); const r4 = rand(); // ~10% accent, ~20% cool, resto hot const isAccent = i % 9 === 1; const isCool = i % 5 === 0 && !isAccent; particles.push({ left: 6 + r1 * 88, // 6-94% del ancho size: 2 + r2 * 3, // 2-5px duration: baseDuration + (r3 - 0.5) * 1.5, // baseDuration ± 0.75s delay: (i / count) * baseDuration + r4 * 0.6, // distribuido en el ciclo hue: isAccent ? palette.accent : isCool ? palette.cool : palette.hot, }); } return particles; } ``` Dos detalles clave: **Delays distribuidos, no random.** Si todos los delays fueran random, podrías tener clumping: varias partículas arrancando juntas y dejando gaps. Aquí cada partícula arranca en una fracción del ciclo (`i / count`), con un pequeño jitter random. Resultado: **flujo continuo, nunca un gap visible**. **Distribución de colores por turnos.** El `i % 9 === 1` y `i % 5 === 0` aseguran proporciones consistentes (~10% accent, ~20% cool, ~70% hot) sin depender del random. Esto hace el efecto visual predecible independientemente de la seed. ## Pieza 2: container query units (cqh) para el movimiento vertical Aquí viene la parte más interesante (y la que tiene un gotcha que te puede costar un rato). Lo intuitivo es animar las partículas con `translateY(-100%)`: ```css @keyframes particle-rise { 0% { transform: translateY(0); } 100% { transform: translateY(-100%); } } ``` **No funciona**. Las partículas vibran en la base sin moverse. ¿Por qué? Porque `translateY(%)` en CSS refiere a la **altura del propio elemento**, no del contenedor. Como cada partícula mide 2-5px, `-100%` solo la mueve 2-5px hacia arriba. Inútil. La solución: **container query units**. Con `cqh` (container query height), `1cqh = 1% de la altura del contenedor con `container-type` definido`. ```css .particle-container { container-type: size; } @keyframes particle-rise { 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(0.5); } 6% { opacity: 1; transform: translate3d(1px, -6cqh, 0) scale(1); } 75% { opacity: 1; transform: translate3d(-3px, -75cqh, 0) scale(0.95); } 90% { opacity: 0.35; transform: translate3d(2px, -90cqh, 0) scale(0.7); } 100% { opacity: 0; transform: translate3d(-1px, -100cqh, 0) scale(0.4); } } ``` Ahora `-100cqh` mueve la partícula **100% de la altura del contenedor padre**: recorre todo el frame sin importar si el contenedor mide 200px o 800px. **Soporte de browsers**: Chrome 105+, Safari 16+, Firefox 110+. Para 2026 esto es seguro. ### La curva de opacidad La curva de opacidad está calibrada para que las partículas **estén brillantes durante el primer 75% del recorrido y se apaguen en el último 25%**. Eso simula brasas reales: viven con fuerza mientras suben, pierden energía al llegar arriba. ``` 0% ───── invisible (en la base) 6% ───── 100% brillo (encendida) 75% ───── 100% brillo (sigue brillante) ←─ empieza a apagarse 90% ───── 35% brillo (apagándose) 100% ──── invisible (en el tope) ``` Si inviertes el rango (apagar al principio, brillar al final) o lo haces simétrico, el efecto pierde el carácter "brasa real". La asimetría es la clave. ## Pieza 3: dirección configurable (up / down) El sistema soporta dos direcciones: partículas que **suben desde la base** (forja) o que **caen desde arriba** (cenizas, nieve, lluvia mágica). La implementación es un par de keyframes espejados más un atributo `data-direction` por partícula: ```css .particle[data-direction="up"] { bottom: 0; animation-name: particle-rise; } .particle[data-direction="down"] { top: 0; animation-name: particle-fall; } @keyframes particle-fall { 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(0.5); } 6% { opacity: 1; transform: translate3d(1px, 6cqh, 0) scale(1); } 75% { opacity: 1; transform: translate3d(-3px, 75cqh, 0) scale(0.95); } 90% { opacity: 0.35; transform: translate3d(2px, 90cqh, 0) scale(0.7); } 100% { opacity: 0; transform: translate3d(-1px, 100cqh, 0) scale(0.4); } } ``` La diferencia entre rise y fall son: - **Anchor**: `bottom: 0` vs `top: 0` - **Signo del translateY**: negativo (sube) vs positivo (cae) Mismo timing, misma curva de opacidad. Misma "vida" de la partícula. En React, el componente decide qué dirección renderizar: ```tsx ``` ## Pieza 4: sistema de palettes configurable Para que el sistema sirva más allá del personaje herrero específico, las partículas son configurables por palette. Algunos presets útiles: ```typescript export const PALETTES = { forge: { hot: 50, cool: 35, accent: 305 }, // smith / fuego arcane: { hot: 270, cool: 240, accent: 180 }, // wizard frost: { hot: 210, cool: 230, accent: 180 }, // ice / arctic shadow: { hot: 20, cool: 290, accent: 130 }, // necromancer snow: { hot: 220, cool: 200, accent: 250, lightness: 0.92, chroma: 0.04, // copos de nieve }, } as const; ``` Cada palette define **tres hues** en OKLCH: - `hot`: el color de la mayoría de las partículas (~70%) - `cool`: variante secundaria (~20%) - `accent`: el color de acento raro (~10%), sirve para incorporar un detalle visual de marca El palette `snow` muestra un detalle extra: además de los hues, override de `lightness` (0.92 en lugar del default 0.68) y `chroma` (0.04 en lugar de 0.22). Eso convierte las "brasas brillantes" en "copos de nieve casi blancos con tinte azul sutil". La misma estructura, diferente carácter visual. ### Por qué OKLCH y no HSL/RGB OKLCH te da control perceptualmente uniforme. Cuando subes lightness de 0.65 a 0.85 en OKLCH, el color realmente se ve "más claro" la misma cantidad para el ojo humano. En HSL eso no pasa: tonos amarillos y azules con la misma "lightness" se ven con brillo muy distinto. Para un sistema donde quieres que `snow` (azul-blanco) se vea igual de brillante que `forge` (naranja), OKLCH es la única opción. ## El componente de capa de partículas Con todas las piezas en lugar, el componente que las junta queda pequeño: ```tsx import type { CSSProperties } from "react"; interface ParticleLayerProps { direction?: "up" | "down"; palette?: PaletteName; intensity?: "quiet" | "medium" | "energetic"; glow?: boolean; seed?: number; count?: number; className?: string; } const INTENSITY_CONFIG = { quiet: { count: 10, baseDuration: 8, glowOpacity: 0.2 }, medium: { count: 18, baseDuration: 5, glowOpacity: 0.35 }, energetic: { count: 26, baseDuration: 3.5, glowOpacity: 0.5 }, } as const; export default function ParticleLayer({ direction = "up", palette = "forge", intensity = "quiet", glow = true, seed = 42, count, className = "", }: ParticleLayerProps) { const config = INTENSITY_CONFIG[intensity]; const paletteHues = PALETTES[palette]; const actualCount = count ?? config.count; const particles = buildParticles(actualCount, config.baseDuration, paletteHues, seed); const lightness = paletteHues.lightness ?? 0.68; const chroma = paletteHues.chroma ?? 0.22; return (
{glow &&
} {particles.map((p, i) => ( ))}
); } ``` Detalles importantes: **Server Component**. No necesita `"use client"` porque no hay state ni event handlers ni `useEffect`. Solo render más CSS. Eso significa que el JS bundle del cliente **no incluye este componente**. **Inline styles via CSS variables**. Cada partícula recibe `--particle-color` como CSS var. Esto permite que el `box-shadow` glow alrededor de la partícula coincida con su color sin tener que duplicarlo: ```css .particle { background: var(--particle-color); box-shadow: 0 0 4px var(--particle-color), 0 0 8px var(--particle-color); } ``` **`aria-hidden`** en el contenedor. Las partículas son decorativas, los lectores de pantalla las ignoran. **`pointer-events-none`** asegura que el layer no bloquea hover o clicks en lo que está debajo o encima. ## Componiendo para casos específicos `ParticleLayer` es la unidad atómica. A partir de ahí construyes patrones específicos según necesites. ### Caso 1: portrait con atmósfera Para mostrar un personaje con partículas alrededor (delante y detrás de la figura): ```tsx function AnimatedPortrait({ src, alt, intensity = "quiet", palette = "forge" }) { // Split del budget total entre back y front (~⅔ + ⅓) const totalCount = INTENSITY_CONFIG[intensity].count; const frontCount = Math.ceil(totalCount / 3); const backCount = totalCount - frontCount; return (
{alt}
); } ``` El detalle de performance aquí: **el total de partículas no cambia** comparado con una sola capa. Si `intensity="quiet"` daría 10 partículas en una sola capa, aquí dan 6 atrás + 4 adelante = 10 totales. La doble capa solo agrega **un `
` de contenedor extra**, no más partículas. Cero impacto de performance, ganancia visual significativa (sparks delante y atrás del personaje). Las seeds distintas (`42` para back, `108` para front) aseguran que las posiciones no se solapen: cada capa tiene su propia "lluvia" de partículas, no duplicados. ### Caso 2: hero con partículas cayendo Para un hero a full viewport con partículas cayendo desde arriba (cenizas, nieve, lluvia mágica): ```tsx
... contenido del hero ...
``` El mismo componente. Diferentes props. Cero condicionales internos. ## Accesibilidad: `prefers-reduced-motion` Una sola regla CSS apaga todo el sistema para usuarios con esa preferencia: ```css @media (prefers-reduced-motion: reduce) { .particle-glow { animation: none; opacity: 0.85; } .particle { animation: none; opacity: 0; } } ``` Nota la decisión: el **glow se mantiene visible pero estático** (sin pulse), las partículas se ocultan completamente. La intuición: el glow es atmósfera ambiental, las partículas son movimiento. Si el usuario pide menos movimiento, sacamos el movimiento pero mantenemos la atmósfera. ## Performance breakdown Para que el costo del sistema quede claro, lo medí. En un portrait con `intensity="quiet"` (10 partículas): | Métrica | Valor | |---|---| | Elementos DOM agregados | 13 (1 contenedor + 1 glow + 10 spans + 1 contenedor front) | | JS runtime cost | 0 (solo render inicial, sin loops) | | JS bundle agregado | 0 (Server Component) | | Repaints por frame | 0 (transforms compositados en GPU) | | CPU usage cuando off-screen | 0 (el browser pausa animaciones fuera del viewport) | Compara eso con un Canvas con `requestAnimationFrame`: | Métrica | Valor | |---|---| | Elementos DOM | 1 (el canvas) | | JS runtime cost | ~16ms cada frame (60fps target) | | JS bundle agregado | ~3-5KB minificado (lógica de física + render loop) | | Repaints | 60/s (entero el canvas) | | CPU usage | Continuo mientras visible | Para sistemas de 10-30 partículas, **CSS gana fácil**. Cuando empiezas a hablar de cientos o miles de partículas con física inter-partícula (colisiones, gravedad real, etc.), Canvas se vuelve necesario. Pero esa no es la mayoría de los casos en una web típica. ## Mejores prácticas ### 1. Usa `cqh` desde el principio, no `vh` ni `%` Si caes en la trampa de `translateY(%)` (que refiere al propio elemento), vas a perder tiempo debugueando por qué las partículas no se mueven. Usa `cqh` desde el primer día. Recuerda agregar `container-type: size` al contenedor padre. ### 2. Pre-computa la distribución de delays Si dejas los delays al random puro, vas a tener gaps visibles donde no hay partículas. Distribúyelos uniformemente con un jitter pequeño: ```typescript delay: (i / count) * baseDuration + r4 * 0.6 ``` Esto garantiza flujo continuo. ### 3. CSS variables para colores por partícula No insertes el color directamente en `background`. Úsalo como `--particle-color` CSS variable. Eso te permite que `box-shadow`, `filter`, o futuras propiedades hereden el color sin duplicar la lógica. ### 4. Server Component cuando puedas Si el componente no necesita state o event handlers, déjalo como Server Component. Bajas bundle y mejoras LCP. El sistema completo de partículas cabe perfecto en SSR. ### 5. Calibra la curva de opacidad, no la dejes lineal El efecto "brasa/spark" depende de que las partículas **estén brillantes la mayor parte del recorrido y se apaguen al final**. Una curva lineal (fade gradual de 100% a 0%) lee como "se apagan desde el principio" y se siente sin vida. Mantén el plateau brillante hasta el 70-80% del recorrido. ## Problemas comunes y soluciones ### "Las partículas no se mueven, solo vibran en la base" **Causa**: estás usando `translateY(-100%)` que refiere al tamaño de la partícula, no del contenedor. **Solución**: cambia a `translateY(-100cqh)` y agrega `container-type: size` al contenedor padre. Verifica soporte de browser si necesitas browsers viejos. ### "Aparece un warning de hydration mismatch" **Causa**: estás usando `Math.random()` o `Date.now()` para generar posiciones. **Solución**: usa un LCG determinista con seed fija. Misma seed = misma secuencia = server y cliente concuerdan. ### "Las partículas se ven 'sintéticas', no parecen brasas reales" **Causa**: probablemente alguna de estas tres cosas: - Curva de opacidad lineal (fade gradual desde el inicio) - Tamaño de partículas demasiado uniforme - Falta de `box-shadow` glow alrededor de cada partícula **Solución**: opacidad en plateau brillante el primer 75%, fade en el último 25%. Varía los tamaños entre `2px` y `5px`. Agrega doble `box-shadow` para halo glow. ### "El loop de animación se ve obvio (se repite igual cada cierto tiempo)" **Causa**: pocas partículas (3-5) con duraciones idénticas. **Solución**: sube el count a 10+ y agrega variación en duration (`baseDuration ± 0.75s`). Con 10 partículas a duraciones ligeramente distintas, el patrón visualmente nunca se repite. ### "El efecto consume batería en mobile" **Causa**: el browser está animando incluso cuando el componente está fuera del viewport. **Solución**: aunque CSS pausa animaciones fuera del viewport automáticamente, puedes ayudar agregando `content-visibility: auto` al contenedor. También verifica que `prefers-reduced-motion` esté respetado. ## Conclusión El instinto inicial de "voy a usar Canvas para partículas" es razonable, pero no siempre es la respuesta correcta. Para efectos atmosféricos con conteos pequeños (~10-30 partículas), CSS puro te da: - **Cero impacto en JS bundle** cuando el componente es Server Component - **Cero costo de runtime** (sin loops) - **SSR safe** desde el primer paint - **Composición limpia** con el resto del DOM - **Accesibilidad declarativa** vía media queries - **Performance superior** para conteos pequeños El costo es que pierdes flexibilidad para física compleja (colisiones, gravedad inter-partícula, comportamientos emergentes). Si tu efecto **no necesita** esas cosas, CSS es la herramienta correcta. ### Checklist para construir tu propio sistema 1. Define qué tan complejo es el efecto que quieres. Si cabe en una curva lineal de keyframes, vas a CSS. 2. Genera posiciones con un LCG determinista (seed fija) para que SSR no se rompa. 3. Distribuye delays uniformemente, no random puro: evita gaps y clumping. 4. Usa `cqh` para movimiento que escale con el contenedor padre. 5. Calibra la curva de opacidad asimétrica (plateau bright + fade tardío). 6. Suma `box-shadow` glow por partícula para sensación de brasa/spark real. 7. Respeta `prefers-reduced-motion` con un media query simple. 8. Si el componente no tiene state, déjalo como Server Component. 9. Si el sistema sirve para múltiples contextos, sepáralo en una primitiva más composiciones específicas. ### Puntos clave a recordar - **El gotcha de `translateY(%)`**: refiere al elemento, no al contenedor. Usa `cqh`. - **`Math.random()` es prohibido en componentes SSR**: usa LCG determinista con seed. - **El "feel" de brasa real está en la curva de opacidad asimétrica**, no en cuántas partículas tengas. - **El sistema de palettes** con `lightness` y `chroma` opcionales te deja construir variantes muy distintas (forge → snow) reutilizando la misma estructura. ## Recursos adicionales - [CSS Container Queries en MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) - [Container query length units (cqh, cqw, cqi, cqb)](https://developer.mozilla.org/en-US/docs/Web/CSS/length#container_query_length_units) - [OKLCH color picker (oklch.com)](https://oklch.com/) - [`prefers-reduced-motion` media feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) - [Numerical Recipes LCG constants](https://en.wikipedia.org/wiki/Linear_congruential_generator) --- ### ¿Han muerto los MCP por culpa de Skills? - URL: https://www.angelcruz.dev/post/han-muerto-los-mcp-por-culpa-de-skills - Markdown: https://www.angelcruz.dev/post/han-muerto-los-mcp-por-culpa-de-skills.md - Categoría: Inteligencia Artificial - Fecha: 2026-03-13 - Excerpt: Skills llegó y muchos declararon a los MCP obsoletos. La realidad es más matizada: no murieron, cambiaron de rol. Te explico por qué. --- title: "¿Han muerto los MCP por culpa de Skills?" excerpt: "Skills llegó y muchos declararon a los MCP obsoletos. La realidad es más matizada: no murieron, cambiaron de rol. Te explico por qué." date: "2026-03-13T00:00:00.000Z" category: "Inteligencia Artificial" seo_title: "¿Han muerto los MCP por culpa de Skills? La verdad sobre el debate" seo_description: "Skills de Claude llegó en octubre 2025 y muchos dijeron que MCP había muerto. Analizamos qué cambió realmente, dónde gana cada uno y cuál usar en tus proyectos." keywords: - "MCP vs Skills" - "Model Context Protocol" - "Claude Skills" - "Agent Skills" - "MCP muerto" - "MCP vs Skills Claude" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/mcp-opengraph-image.png" --- En octubre de 2025, Anthropic lanzó [Agent Skills](https://claude.com/blog/equipping-agents-for-the-real-world-with-agent-skills). En horas, los feeds se llenaron de un mismo titular: los MCP han muerto. Newsletters, hilos de X y hasta posts de ingenieros que meses antes evangelizaban el protocolo empezaron a escribir obituarios. ¿Pero es cierto? ¿O es clickbait disfrazado de análisis técnico? La respuesta honesta es que los MCP no murieron. Lo que murió fue un caso de uso específico donde MCP se usaba para cosas para las que nunca fue diseñado. Skills llegó a ocupar ese espacio con una solución más simple. Y esa distinción importa, porque si confundes las dos cosas vas a tomar decisiones arquitectónicas equivocadas. ## Qué pasó: el momento en que todos declararon a los MCP muertos MCP (Model Context Protocol) lo publicó Anthropic en noviembre de 2024 como un estándar abierto para conectar modelos de lenguaje a herramientas externas. La promesa era clara: un protocolo único que cualquier modelo podía implementar para hablar con cualquier herramienta. Once meses después, Anthropic lanzó Skills. La mecánica es completamente diferente: en lugar de un protocolo de comunicación, Skills son archivos Markdown con instrucciones en lenguaje natural. Describes cómo debe comportarse el agente en un dominio específico, y el modelo lo sigue. Sin servidores, sin transports, sin JSON-RPC. El timing fue lo que desencadenó el debate. Porque si MCP requería horas de configuración para conectar una API y Skills resolvía casos similares en minutos con un archivo de texto, la pregunta natural era: ¿para qué sirve MCP ahora? Lo que la mayoría omitió en esa discusión es que Skills hace en 5 minutos lo que MCP hace en 50.000 tokens. Eso no es un insulto a MCP; es una descripción de dos herramientas con propósitos distintos. ## El problema real de MCP que nadie quería admitir El protocolo tiene un problema de costos de contexto que estaba ahí desde el principio pero que pocos discutían abiertamente. Un servidor como el MCP de GitHub puede consumir más de 50.000 tokens solo en JSON schemas al iniciar la conexión. Eso es contexto que el modelo necesita procesar antes de hacer nada útil. Anthropic publicó cómo reducir los costos de MCP un 98,7% con ejecución de código, y parte de la comunidad lo leyó como una admisión de culpa: si necesitas reducir el costo un 98,7%, algo estaba muy mal. En cierto modo tenían razón, aunque la solución que propusieron (el "Code Mode" con dos herramientas: búsqueda semántica + ejecución en sandbox) es elegante. Los benchmarks tampoco ayudaron. Claude Haiku 4.5 tiene solo un 59,8% de precisión en lookups de datos en contextos MCP grandes. Cuando el servidor carga decenas de herramientas y miles de tokens de schema, la capacidad del modelo para seleccionar la herramienta correcta se degrada. No es un bug de MCP, es física cognitiva: más contexto no siempre es mejor. Y encima, la complejidad de implementación. MCP requiere entender y construir sobre una arquitectura de hosts, clients, servers y transports. Skills es markdown. Para muchos equipos, la relación esfuerzo/resultado no cerraba. ## Dónde Skills gana claramente - **Workflows procedurales y conocimiento de dominio**: si quieres enseñarle al agente cómo hace las cosas tu equipo (convenciones de código, proceso de code review, estructura de tickets), Skills es la herramienta correcta. Eso no es conectividad externa, es instrucción contextual. - **Automatizaciones internas que no necesitan actualización dinámica**: si el proceso no cambia frecuentemente, no necesitas un servidor que lo descubra dinámicamente. - **Reemplazar MCPs que eran solo wrappers de CLIs o APIs simples**: si tu servidor MCP básicamente ejecutaba `git status` o llamaba a un endpoint REST con parámetros fijos, Skills puede reemplazarlo con menos complejidad. - **Costo de contexto bajo**: solo la descripción del Skill se carga en el contexto inicial (unos 100 tokens). Las instrucciones completas se activan solo cuando el agente detecta que ese Skill es relevante. La postura de [Simon Willison](https://simonwillison.net/2025/Oct/16/claude-skills/) al analizar el lanzamiento fue pragmática: Skills hace que la personalización de comportamiento sea accesible para personas que no son ingenieros. No es una amenaza existencial para MCP; es democratización de una capa diferente del stack. ## Dónde MCP sigue ganando Aquí es donde la narrativa "MCP is dead" falla más. Hay casos de uso donde MCP no tiene sustituto razonable: - **Interoperabilidad multiplataforma**: MCP funciona en Claude, ChatGPT, Cursor, Gemini, VS Code Copilot y decenas de herramientas más. Skills es Claude-only. Si tu equipo usa múltiples modelos o planeas hacerlo, MCP es la única opción que no te ata a un vendor. - **APIs grandes con discovery dinámico**: el "Code Mode" que describe el [análisis de Codely](https://codely.com/en/blog/have-mcps-died-because-of-skills) —dos herramientas (búsqueda semántica + ejecución en sandbox) que dan acceso a APIs enormes sin cargar todo el schema— sigue siendo MCP. Skills no puede hacer eso. - **Permisos y seguridad a nivel de protocolo**: MCP permite restricciones nativas como acceso de solo lectura a una base de datos o alcance limitado por usuario. Con Skills no hay ese nivel de control estructural. - **Actualización automática**: un servidor MCP refleja los cambios en una API automáticamente porque los descubre en tiempo de ejecución. Un Skill con instrucciones hard-codeadas necesita mantenimiento manual cuando cambia algo externo. - **Adopción del ecosistema**: 97 millones de descargas mensuales del SDK, más de 10.000 servidores activos. OpenAI adoptó MCP en marzo de 2025, Google DeepMind en abril, y la Linux Foundation lo tomó bajo su gobernanza en diciembre de 2025 junto con OpenAI y Block como co-fundadores. Eso no es el movimiento de una tecnología moribunda. ## La postura oficial de Anthropic: complementarios, no competidores El propio blog de Anthropic sobre Skills es explícito: "Skills can complement MCP servers by teaching agents more complex workflows that involve external tools and software." No "reemplazar". Complementar. El 18 de diciembre de 2025, Skills se convirtió en estándar abierto, igual que hicieron con MCP. Anthropic publicó la especificación y la dejó libre para que otros la adopten. La lectura correcta de eso no es "MCP murió, ahora Skills es el estándar". Es que Anthropic está construyendo un stack de dos capas y quiere que ambas sean abiertas. La analogía más útil que encontré en el análisis de [Armin Ronacher](https://lucumr.pocoo.org/2025/12/13/skills-vs-mcp/) es esta: MCP es la fontanería, Skills es el manual de instrucciones. Puedes tener el mejor manual del mundo, pero si no hay cañerías, el agua no llega. Y las cañerías sin manual terminan en un agente que no sabe qué hacer con los datos que obtuvo. ## Una advertencia sobre seguridad en producción Antes de decidir qué usar, hay algo que no se puede ignorar. El servidor Git oficial de Anthropic tuvo tres CVEs documentados en 2025, incluyendo path traversal y ejecución de código arbitrario. El paquete `mcp-remote` tuvo el CVE-2025-6514 con CVSS 9.6, un RCE vía flujo OAuth. Y según auditorías del ecosistema, el 53% de los servidores MCP activos usan API keys estáticas de larga vida en lugar de OAuth con alcance limitado. Nada de esto es razón para no usar MCP. Sí es razón para auditarlo antes de exponerlo en producción, especialmente si el servidor tiene acceso a datos sensibles o ejecuta comandos en tu infraestructura. ## El sistema de dos niveles que nadie esperaba > Los MCP no murieron. Encontraron su lugar correcto en el stack. La forma más clara de verlo es comparar los dos en los ejes que importan: | | Skills | MCP | |---|---|---| | Mejor para | Workflows procedurales, conocimiento de equipo | APIs externas, acceso a datos, multiplataforma | | Costo de contexto | Muy bajo (~100 tokens iniciales) | Alto (puede llegar a 50k+ tokens) | | Complejidad de implementación | Baja (markdown) | Alta (protocolo) | | Actualización automática | No | Sí | | Compatible con otros modelos | No (Claude-only) | Sí (OpenAI, Gemini, Cursor...) | | Casos de uso ideales | Procedimientos internos, automatización de dev | Bases de datos, grandes APIs, equipos multi-modelo | ## ¿Cuál deberías usar en tus proyectos? La decisión no es filosófica, es operativa: - Si tu equipo solo usa Claude: usa Skills para workflows y conocimiento de dominio. Agrega MCP solo cuando necesites discovery dinámico de herramientas o acceso a APIs que cambian. - Si tu equipo usa múltiples modelos o planea hacerlo: MCP sigue siendo la elección para integraciones externas. No existe un equivalente de Skills para otros modelos todavía. - Si tienes un servidor MCP que básicamente es un wrapper de una CLI o un endpoint REST simple: evalúa migrarlo a un Skill. La reducción de complejidad operativa probablemente valga la pena. - Si tienes una API grande y compleja con docenas de endpoints: el "Code Mode" de MCP (búsqueda semántica + ejecución en sandbox) es la arquitectura correcta. No lo abandones por seguir una tendencia. La regla práctica: si lo que quieres enseñar es *cómo* hacer algo, Skills. Si lo que quieres es *acceso* a algo, MCP. El verdadero perdedor en este debate no fue MCP como tecnología. Fue el patrón de usar MCP como proxy de todo, incluyendo casos donde un archivo Markdown con instrucciones hubiera sido suficiente desde el principio. Ese caso de uso específico sí murió, y está bien que haya muerto. Si todavía no conoces MCP en profundidad, [lee la introducción que publiqué aquí](/post/introduccion-a-mcp-model-context-protocol) antes de decidir si lo descartas o no. ## Preguntas Frecuentes **¿Skills reemplaza completamente a MCP?** No. Son capas distintas del stack. Skills maneja procedimientos y conocimiento de dominio: le enseña al agente cómo hacer las cosas. MCP maneja conectividad con sistemas externos: le da al agente acceso a herramientas y datos. Puedes y debes usar ambos juntos. **¿Puedo usar Skills y MCP al mismo tiempo?** Sí, y es el patrón recomendado por Anthropic. MCP actúa como infraestructura de conectividad (acceso a bases de datos, APIs, herramientas externas), mientras que Skills actúa como capa de instrucciones encima: le dice al agente cómo usar esa infraestructura en el contexto específico de tu equipo o producto. **¿MCP tiene futuro si ya lo adoptó la Linux Foundation?** La gobernanza bajo la Linux Foundation en diciembre de 2025, con OpenAI y Block como co-fundadores del proyecto, es la señal más fuerte de que MCP llegó para quedarse. Los estándares que entran a la Linux Foundation no mueren: se estandarizan. El debate "MCP is dead" probablemente envejecerá muy mal. --- ### Descubre las novedades de Laravel 13 - URL: https://www.angelcruz.dev/post/laravel-13-novedades - Markdown: https://www.angelcruz.dev/post/laravel-13-novedades.md - Categoría: Laravel - Fecha: 2026-03-04 - Excerpt: Laravel 13 fue lanzado el 17 de marzo de 2026 con PHP 8.3 mínimo, el Laravel AI SDK de primera parte, soporte JSON:API, búsqueda vectorial, passkeys y mucho más. Guía completa de novedades y breaking changes. --- title: "Descubre las novedades de Laravel 13" excerpt: "Laravel 13 fue lanzado el 17 de marzo de 2026 con PHP 8.3 mínimo, el Laravel AI SDK de primera parte, soporte JSON:API, búsqueda vectorial, passkeys y mucho más. Guía completa de novedades y breaking changes." date: "2026-03-04T10:00:00.000Z" lastModified: "2026-03-23T00:00:00.000Z" category: "Laravel" seo_title: "Laravel 13: todas las novedades y cambios del lanzamiento oficial" seo_description: "Laravel 13 lanzado el 17 de marzo de 2026. PHP 8.3 mínimo, Laravel AI SDK, soporte JSON:API nativo, búsqueda vectorial con pgvector, passkeys, Server-Sent Events y más. Guía completa." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- **Laravel 13 fue lanzado oficialmente el 17 de marzo de 2026.** Actualicé este artículo con todas las novedades confirmadas tras el lanzamiento. Si lo leíste antes, vas a encontrar bastante información nueva: el AI SDK de primera parte, soporte JSON:API, búsqueda vectorial y passkeys son los titulares que no estaban antes. ## Fecha de lanzamiento **Laravel 13 salió el 17 de marzo de 2026**, cumpliendo la tradición de releases anuales en el primer trimestre. El soporte oficial es el siguiente: | Versión | PHP | Lanzamiento | Bug fixes hasta | Seguridad hasta | |---------|-----|-------------|-----------------|-----------------| | 11 | 8.2–8.4 | Mar 2024 | Sep 2025 | Mar 2026 | | 12 | 8.2–8.5 | Feb 2025 | Ago 2026 | Feb 2027 | | **13** | **8.3–8.5** | **17 Mar 2026** | **Q3 2027** | **17 Mar 2028** | ## PHP 8.3 como mínimo Este sigue siendo el único **breaking change real** que afectará a la mayoría. **Laravel 13 requiere PHP 8.3 como mínimo**, abandonando el soporte para PHP 8.2. ```bash # Versiones PHP que soporta Laravel 13 PHP 8.3 PHP 8.4 PHP 8.5 ``` Soltar PHP 8.2 no es solo una decision administrativa: permite usar readonly classes, typed class constants y `#[\Override]` de forma nativa, sin polyfills. El paquete `symfony/polyfill-php83` fue eliminado de las dependencias. Las primeras mediciones muestran mejoras de 5–10% en velocidad solo por ese motivo. Si tu servidor todavía corre PHP 8.2, tendrás que quedarte en Laravel 12 o actualizar el runtime primero. ## Laravel AI SDK — primera parte, estable en producción Esta es la novedad más grande de Laravel 13 y no estaba en los primeros reportes pre-lanzamiento. **Laravel incluye ahora un SDK de inteligencia artificial de primera parte** (`laravel/ai`) que llega estable con esta versión. El SDK ofrece una interfaz unificada, agnóstica del proveedor, para: - Generación de texto y agentes con tool-calling - Generación de imágenes - Síntesis de audio - Generación de embeddings - Integración con vector stores La filosofía es la misma de siempre en Laravel: abstraer la complejidad y que el código sea expresivo sin importar si usas OpenAI, Anthropic u otro proveedor por debajo. ## PHP Attributes — ahora en 15+ ubicaciones El artículo original cubría los Attributes en modelos y jobs. Con el lanzamiento oficial, la cobertura es mucho más amplia: **más de 15 ubicaciones** en el framework. ### Attributes en modelos Eloquent ```php use Illuminate\Database\Eloquent\Attributes\Table; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; #[Table('posts')] #[Fillable('title', 'body', 'published_at')] #[Hidden('internal_notes')] class Post extends Model { // } ``` ### Attributes en Queue Jobs ```php use Illuminate\Queue\Attributes\Connection; use Illuminate\Queue\Attributes\Queue; use Illuminate\Queue\Attributes\Tries; use Illuminate\Queue\Attributes\Backoff; use Illuminate\Queue\Attributes\Timeout; #[Connection('redis')] #[Queue('orders')] #[Tries(3)] #[Backoff(60)] #[Timeout(120)] class ProcessOrder implements ShouldQueue { // } ``` ### Attributes en controladores ```php use Illuminate\Routing\Attributes\Middleware; use Illuminate\Auth\Attributes\Authorize; #[Middleware('throttle:api')] #[Authorize('view-dashboard')] class DashboardController extends Controller { // } ``` Los Attributes también están disponibles en console commands, form requests, API resources, listeners, mailables, notifications, factories y test seeders. **No es un cambio breaking**: ambas formas (properties y Attributes) funcionan. Puedes migrar gradualmente o no migrar nunca. ## Soporte JSON:API de primera parte Laravel 13 introduce clases de recursos JSON:API nativas que manejan: - Serialización de objetos de respuesta - Inclusión de relaciones - Sparse fieldsets - Links - Headers `Content-Type: application/vnd.api+json` correctos Esto elimina la necesidad de paquetes de terceros como `laravel-json-api` o `frappe/laravel-json-api` para proyectos que consumen este estándar. ## Queue routing centralizado por clase En lugar de definir la conexión y la cola dentro de cada job, ahora puedes centralizar todo eso en un service provider: ```php use App\Jobs\ProcessPodcast; use Illuminate\Support\Facades\Queue; Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts'); ``` Menos repetición, configuración en un solo lugar. Una mejora pequeña pero muy práctica en proyectos con muchos jobs. ## Búsqueda vectorial / semántica con pgvector Laravel 13 agrega soporte nativo para búsqueda vectorial usando PostgreSQL y la extensión `pgvector`: ```php DB::table('documents') ->whereVectorSimilarTo('embedding', 'Las mejores bodegas de la Rioja') ->limit(10) ->get(); ``` El ORM se encarga de construir la query. Muy relevante si estás construyendo aplicaciones con RAG (Retrieval-Augmented Generation) o cualquier búsqueda semántica. ## Cache::touch() para extender TTL sin re-almacenar Pequeña pero útil. `Cache::touch()` extiende el tiempo de expiración de un item en caché **sin necesidad de obtenerlo ni re-almacenarlo**: ```php // Extiende el TTL 60 minutos más sin leer ni escribir el valor Cache::touch('session_data', now()->addMinutes(60)); ``` Antes tenías que hacer esto: ```php $data = Cache::get('session_data'); Cache::put('session_data', $data, now()->addMinutes(60)); ``` Con `Cache::touch()` eliminas el round-trip innecesario. Funciona en todos los drivers: Redis, Memcached, Database y File. Retorna `true` si la clave existe o `false` si no la encuentra. ## Passkey authentication Soporte para passkeys integrado directamente en los starter kits y en Laravel Fortify. Ya no necesitas un paquete separado para implementar autenticación sin contraseña basada en WebAuthn/FIDO2. ## Server-Sent Events con Response::eventStream() ```php return response()->eventStream(function () { foreach ($events as $event) { yield $event; } }); ``` Un método nativo en `ResponseFactory` para streaming de eventos desde el servidor al cliente. Útil para notificaciones en tiempo real sin la complejidad de WebSockets cuando la comunicación es unidireccional. ## Breaking changes Laravel 13 tiene pocos breaking changes reales pero hay varios que conviene revisar antes de migrar. ### Alto impacto **PHP 8.2 eliminado.** El único breaking change que afectará a la mayoría. **Dependencias en `composer.json`** que debes actualizar: ```bash laravel/framework → ^13.0 laravel/tinker → ^3.0 phpunit/phpunit → ^12.0 pestphp/pest → ^4.0 ``` **CSRF middleware renombrado.** `VerifyCsrfToken` ahora se llama `PreventRequestForgery`. También agrega verificación de origen via el header `Sec-Fetch-Site`. Los aliases antiguos están deprecados, no eliminados, así que no romperá inmediatamente, pero conviene actualizar. **Deserialización de objetos en caché restringida por defecto.** Si almacenas objetos PHP en caché, ahora necesitas allowlistear explícitamente las clases permitidas en la configuración. Una medida de seguridad correcta. ### Impacto medio **Prefijos de caché y Redis cambiaron de formato**: de guión bajo (`app_cache_`) a guión (`app-cache-`). Si usas prefijos, define `CACHE_PREFIX`, `REDIS_PREFIX` y `SESSION_COOKIE` explícitamente en `.env` para evitar que las claves existentes queden huérfanas. **Nombre de la cookie de sesión**: ahora usa `Str::snake()` en lugar de `Str::slug()`. Revisa si tienes algo que dependa del nombre exacto de esa cookie. ### Bajo impacto **`Model::boot()` no puede instanciar el mismo modelo.** Ya no se pueden crear nuevas instancias del modelo dentro de su propio `boot()`. Si tienes ese patrón, lanzará `LogicException`. Hay que refactorizar esa lógica. **Rutas con dominio se registran primero** que las rutas sin dominio, mejorando la consistencia en la resolución. **Nombres de vistas de paginación cambiaron**: `pagination::default` → `pagination::bootstrap-3`, `pagination::simple-default` → `pagination::simple-bootstrap-3`. **Eventos de cola cambiaron**: `JobAttempted::$exceptionOccurred` (bool) ahora es `$exception` (object|null). `QueueBusy::$connection` ahora es `$connectionName`. **`Container::call()`** ahora respeta los defaults de parámetros nullable (retorna `null` en vez de intentar resolver un binding). **MySQL DELETE con JOIN** ahora incluye cláusulas `ORDER BY` y `LIMIT`. Puede surfacear un `QueryException` en MySQL/MariaDB si tenías queries que dependían del comportamiento anterior. **Tablas pivot polimórficas**: los nombres de tabla ahora se pluralizan para modelos pivot personalizados. Agrega la propiedad `$table` explícita para evitar problemas. ### Métodos eliminados Estos métodos estaban deprecados desde L11/L12 y ahora fueron removidos: - `Route::controller()` — usar `Route::resource()` o definiciones explícitas - `$request->has()` con sintaxis de array — usar `$request->hasAny()` - `Model::unguard()` — usar `Model::preventSilentlyDiscardingAttributes()` - El patrón `app/Http/Kernel.php` — fue deprecado en L11 y ahora está completamente eliminado ## Resumen de novedades | Caracteristica | Estado | |----------------|--------| | PHP 8.3 minimo | Confirmado (breaking change) | | Laravel AI SDK (`laravel/ai`) | Nuevo — primera parte | | PHP Attributes en 15+ ubicaciones | Confirmado | | Soporte JSON:API nativo | Nuevo | | Queue routing centralizado por clase | Nuevo | | Busqueda vectorial con pgvector | Nuevo | | Cache::touch() | Confirmado | | Passkey authentication | Nuevo | | Server-Sent Events nativo | Nuevo | | Restriccion en Model::boot() | Breaking change menor | | Symfony 7.4 / 8.0 | Confirmado | | CSRF middleware renombrado | Breaking change menor | ## Migracion desde Laravel 12 La estimacion oficial es aproximadamente **10 minutos** para la mayoria de aplicaciones. Si tienes **Laravel Boost** instalado, puedes usar el comando `/upgrade-laravel-v13` directamente desde tu editor (funciona con Claude Code, Cursor y VS Code). Tambien esta disponible el upgrade automatizado via [LaravelShift](https://laravelshift.com). Los pasos manuales basicos son: 1. Verificar que tienes PHP 8.3+ 2. Actualizar `composer.json` con las versiones nuevas 3. Revisar el upgrade guide oficial si tienes codigo que usa los patrones afectados 4. Correr `composer update` --- ## Preguntas frecuentes ### ¿Cuando salio Laravel 13? El **17 de marzo de 2026**, siguiendo la tradicion de Q1 releases del framework. ### ¿Que version de PHP necesito para Laravel 13? **PHP 8.3 como minimo.** Tambien soporta PHP 8.4 y 8.5. Si estas en PHP 8.2 tendras que quedarte en Laravel 12 o actualizar el runtime primero. ### ¿Los PHP Attributes reemplazan las propiedades de clase en los modelos? No, son una **alternativa opcional**. Puedes seguir usando `$fillable`, `$hidden`, `$table` exactamente como antes. Los Attributes son otra forma de hacer lo mismo, no un reemplazo obligatorio. ### ¿La migracion desde Laravel 12 es complicada? En principio no. El principal requisito es PHP 8.3. El resto de cambios son aditivos o cambios menores. Los puntos que pueden requerir atencion son el prefijo de cache, el CSRF middleware renombrado y la restriccion en `Model::boot()`. ### ¿Cache::touch() funciona con todos los drivers de cache? Si. `Cache::touch()` es compatible con Redis, Memcached, Database y File. ### ¿El Laravel AI SDK requiere un proveedor especifico? No. Es agnóstico del proveedor. Puedes usarlo con OpenAI, Anthropic u otros sin cambiar el código de tu aplicación. --- ## Referencias - [Release Notes | Laravel 13.x — Documentacion oficial](https://laravel.com/docs/13.x/releases) - [Upgrade Guide | Laravel 13.x — Documentacion oficial](https://laravel.com/docs/13.x/upgrade) - [Laravel 13 Released — Laravel News](https://laravel-news.com/laravel-13-released) - [What We Know About Laravel 13 — Laravel News](https://laravel-news.com/laravel-13) - [Laravel 13 released: features and upgrade guide — Benjamin Crozat](https://benjamincrozat.com/laravel-13) - [Laravel Versions Timeline — laravelversions.com](https://laravelversions.com/en) - [PHP Attributes — PHP Manual](https://www.php.net/manual/es/language.attributes.overview.php) --- ### IA Semanal: 16-23 Febrero 2026 - URL: https://www.angelcruz.dev/post/ia-semanal-16-23-febrero-2026 - Markdown: https://www.angelcruz.dev/post/ia-semanal-16-23-febrero-2026.md - Categoría: Inteligencia Artificial - Fecha: 2026-02-23 - Excerpt: Resumen completo de la semana en IA: Claude Opus 4.6 con 1M tokens, ChatGPT retira GPT-4o, Gemini 3.1 Pro rompe benchmarks, Grok 4.2 beta, y la explosión de modelos chinos. --- title: "IA Semanal: 16-23 Febrero 2026" excerpt: "Resumen completo de la semana en IA: Claude Opus 4.6 con 1M tokens, ChatGPT retira GPT-4o, Gemini 3.1 Pro rompe benchmarks, Grok 4.2 beta, y la explosión de modelos chinos." date: "2026-02-23T19:00:00.000Z" category: "Inteligencia Artificial" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" seo_title: "IA Semanal 16-23 Feb 2026: Claude 4.6, GPT-5.2, Gemini 3.1 Pro" seo_description: "Resumen semanal de IA: Claude Opus 4.6 con 1M tokens, retiro de GPT-4o, Gemini 3.1 Pro líder en benchmarks, Grok 4.2 beta, Claude Code Security, y modelos chinos sorprenden." keywords: - inteligencia artificial - ia semanal - claude opus 4.6 - chatgpt gpt-5.2 - gemini 3.1 pro - grok 4.2 - ai news featured: true --- Bienvenido al **resumen semanal de IA** del 16 al 23 de febrero de 2026. Esta semana estuvo cargada: Anthropic lanzó dos modelos en menos de dos semanas, OpenAI retiró GPT-4o, Google Gemini lideró benchmarks, y China sorprendió con Seedance 2.0. ## Anthropic: Dos Lanzamientos en Una Semana ### Claude Opus 4.6 (5 de Febrero) El **5 de febrero**, Anthropic lanzó [Claude Opus 4.6](https://www.cnbc.com/2026/02/17/anthropic-ai-claude-sonnet-4-6-default-free-pro.html), su modelo más potente hasta la fecha. Las novedades principales: - **1M Context Window (Beta)**: primera vez en un modelo clase Opus - **128K Output**: permite tareas de generación larga - **Autonomous Agent Teams**: múltiples instancias dividen el trabajo y lo completan en paralelo Con Autonomous Agent Teams, en lugar de un solo modelo procesando todo, Opus 4.6 coordina equipos de agentes que trabajan simultáneamente en diferentes partes de una tarea. ### Claude Sonnet 4.6 (17 de Febrero) Apenas **12 días** después, Anthropic lanzó [Claude Sonnet 4.6](https://www.cnbc.com/2026/02/17/anthropic-ai-claude-sonnet-4-6-default-free-pro.html) con mejoras en: - Computer Use (control de interfaces de usuario) - Coding con mejor comprensión de contextos grandes - Design y knowledge work - Procesamiento de grandes volúmenes de datos Dos modelos mayores en menos de dos semanas es un ritmo inusual incluso para Anthropic. ### Claude Code: $2.5 Mil Millones en Revenue Anual [Claude Code](https://www.bloomberg.com/news/articles/2026-02-20/the-surprise-hit-that-made-anthropic-into-an-ai-juggernaut-mlve4nc2) alcanzó $1 mil millones en revenue anualizado en sus primeros 6 meses, y actualmente está en $2.5 mil millones (duplicó en ~6 meses). El 80% de los clientes son enterprise. Lanzado públicamente hace un año, Claude Code lidera el mercado de programación asistida por IA. ### Claude Code Security (20 de Febrero) Anthropic lanzó [Claude Code Security](https://thehackernews.com/2026/02/anthropic-launches-claude-code-security.html), una nueva capacidad que escanea repositorios en busca de vulnerabilidades y sugiere parches para revisión humana. Disponible en preview en Claude Code web. Las acciones de varias empresas de ciberseguridad cayeron tras el anuncio. ### Tensiones con el Pentágono La semana también trajo [controversia](https://www.nbcnews.com/tech/security/anthropic-ai-defense-war-venezuela-maduro-rcna259603): el Pentágono amenaza con designar a Anthropic como "supply chain risk" si no elimina restricciones de uso militar. El conflicto escaló tras el **3 de enero**, cuando fuerzas especiales de EE.UU. capturaron a Nicolás Maduro en Venezuela, y reportes indican que usaron Claude durante la operación vía Palantir (partner de Anthropic). **Financiamiento:** Anthropic cerró una ronda de **$30 mil millones** con valuación de **$380 mil millones**. --- ## OpenAI: Adiós a GPT-4o, Hola GPT-5.2 ### Retiro de Modelos Antiguos (13 de Febrero) El **13 de febrero**, OpenAI [retiró oficialmente](https://openai.com/index/retiring-gpt-4o-and-older-models/) GPT-4o, GPT-4.1, GPT-4.1 mini, OpenAI o4-mini, y GPT-5 (Instant y Thinking). Todas las conversaciones y proyectos ahora usan **GPT-5.2** por defecto. ### Nuevas Características de ChatGPT [ChatGPT recibió varias mejoras](https://releasebot.io/updates/openai/chatgpt): - **Context Window ampliado**: 256K tokens totales (128K input + 128K output) en modo Thinking, antes 196K - **Más archivos**: hasta 20 simultáneos (antes 10) - **Deep Research mejorado**: enfoque en sitios específicos como fuentes confiables, sidebar rediseñado y vista fullscreen - **Voice mejorado**: mejor seguimiento de instrucciones y uso de herramientas como búsqueda web - **Code Blocks interactivos**: escribe, edita y previsualiza código en un solo lugar ### ChatGPT Agent (Para Pro Users) **ChatGPT Agent** empezó a rodar para usuarios Pro, con acceso para Plus y Team "en los próximos días". Es la respuesta de OpenAI a los Autonomous Agents de Claude Opus 4.6. ### Seguridad y Ads OpenAI agregó Lockdown Mode para usuarios de alta seguridad y etiquetas de "Elevated Risk" en toda la plataforma. También está testeando publicidad para usuarios Free y Go en EE.UU. Los planes pagos (Plus, Pro, Business, Enterprise, Education) no muestran ads. --- ## Google Gemini: Líder en Benchmarks ### Gemini 3.1 Pro (19 de Febrero) El **19 de febrero**, Google lanzó [Gemini 3.1 Pro](https://ai.google.dev/gemini-api/docs/changelog) con resultados destacados en benchmarks: lidera 13 de 16 tests y obtuvo 77.1% en ARC-AGI-2, el puntaje más alto registrado en ese benchmark de razonamiento abstracto. ### Gemini 3 Flash - Nuevo Default **Gemini 3 Flash** es ahora el modelo default en la app Gemini. Según Google, es un upgrade significativo vs Gemini 2.5 Flash en velocidad e inteligencia. ### Gemini 3 Deep Think (AI Ultra) Suscriptores de [Google AI Ultra](https://9to5google.com/2026/02/21/google-ai-pro-ultra-features/) tienen acceso a **Gemini 3 Deep Think**, el modo de razonamiento extendido de Gemini. Disponible en la app Gemini y via API (con solicitud para empresas). ### Personal Intelligence (Beta) [Personal Intelligence](https://theagencyjournal.com/geminis-february-2026-updates-deep-think-personal-intelligence-and-what-actually-changes-for-you/) está disponible para suscriptores AI Pro y AI Ultra en EE.UU. Conecta datos de Gmail, Google Photos, YouTube y Search. Es opt-in (el usuario decide si activar) y funciona en web, Android e iOS. ### Gemini en Chrome Disponible para suscriptores AI Pro/Ultra en EE.UU. con Chrome en inglés. ### Workspace y Educación Google agregó un add-on de AI Expanded Access para uso aumentado en Workspace apps, y capacidades generativas para usuarios 18+ con Education Plus o Teaching & Learning. --- ## xAI: Grok 4.2 Beta con 4 Agentes xAI lanzó [Grok 4.2 beta](https://www.marketingprofs.com/opinions/2026/54328/ai-update-february-20-2026-ai-news-and-views-from-the-past-week) con una arquitectura de cuatro agentes especializados que colaboran, debaten conclusiones y sintetizan respuestas antes de presentarlas. Según xAI, esto resultó en una reducción del 65% en alucinaciones comparado con versiones previas. El enfoque multi-agente es similar al de Claude Opus 4.6. --- ## China: Modelos que Compiten con Occidente ### Seedance 2.0 - ByteDance [Seedance 2.0](https://www.cnn.com/2026/02/20/china/china-ai-seedance-intl-hnk-dst) de ByteDance se volvió viral esta semana con videos cinematográficos de celebridades en situaciones absurdas. El modelo genera videos realistas en minutos y fue descrito como uno de los más avanzados en su tipo. El lanzamiento "asustó a Hollywood" según CNN. ### Doubao 2.0 - ByteDance ByteDance también lanzó **Doubao 2.0**, orientado a la ejecución de tareas complejas y multistep (no solo respuestas a preguntas). ### Kimi K2.5 - Moonshot AI Moonshot AI presentó **Kimi K2.5** con capacidades de generación de video y manejo autónomo de tareas. ### MiniMax: M2.5 y M2.5 Lightning La startup MiniMax lanzó **M2.5** y **M2.5 Lightning** con rendimiento cercano a los modelos líderes a una fracción del costo, usando arquitectura Mixture of Experts. --- ## Mistral AI: Voxtral Transcribe 2 El **5 de febrero**, Mistral AI lanzó [Voxtral Transcribe 2](https://llm-stats.com/llm-updates) con un modelo compacto (Voxtral Mini Transcribe), una versión open-source en tiempo real (Voxtral Realtime), menos de 200ms de latencia, soporte para 13 idiomas y la posibilidad de correr localmente en teléfonos y laptops. --- ## Tavus: Phoenix-4 Tavus lanzó [Phoenix-4](https://www.marketingprofs.com/opinions/2026/54328/ai-update-february-20-2026-ai-news-and-views-from-the-past-week), un modelo generativo de video basado en Gaussian-diffusion. Renderiza avatares completos (cabeza y hombros) a 40 fps en 1080p, con control emocional explícito, comportamiento de escucha activa y movimiento facial continuo. Orientado a customer service, presentaciones virtuales y asistentes digitales. --- ## Financiamiento y Movimientos Estratégicos ### Fei-Fei Li's World Labs: $1 Mil Millones [World Labs](https://www.marketingprofs.com/opinions/2026/54328/ai-update-february-20-2026-ai-news-and-views-from-the-past-week), la startup de Fei-Fei Li, cerró **$1 mil millones** en financiamiento de AMD, NVIDIA y Fidelity. El objetivo es avanzar en inteligencia espacial con productos como **MARBLE**, que genera mundos 3D coherentes desde imágenes, video o texto. ### Tech Corps - Casa Blanca La [Casa Blanca anunció](https://www.cnbc.com/2026/02/23/us-launch-peace-corps-tech-corps-india-export-ai-stack-sovereignty-counter-china.html) el **"Tech Corps"**, una iniciativa dentro del Peace Corps para promover IA americana en el extranjero, ayudar a naciones partner a adoptar tecnología americana y contrarrestar la influencia china en IA. --- ## Empresariales ### Databricks: Agent Bricks GA Databricks hizo [Agent Bricks Custom Agents](https://www.marketingprofs.com/opinions/2026/54328/ai-update-february-20-2026-ai-news-and-views-from-the-past-week) generalmente disponible. Permite construir, testear y desplegar agentes de IA de calidad producción, con apps de Databricks gestionadas y serverless compute. ### Apple: Push a IA Visual Según [Bloomberg](https://www.bloomberg.com/news/newsletters/2026-02-22/apple-s-ai-wearables-push-what-to-expect-from-march-4-low-end-macbook-launch), Apple está trabajando en un avance de inteligencia artificial visual. Se espera más información en el evento del **4 de marzo**. --- ## Lo Que Viene en 2026 Según [TechCrunch](https://techcrunch.com/2026/01/02/in-2026-ai-will-move-from-hype-to-pragmatism/), 2026 es el año en que la IA pasa de hype a pragmatismo: adopción empresarial real (no solo POCs), ROI medible en implementaciones, consolidación de proveedores y regulación más clara. --- ## Comparativa de Modelos Actuales | Modelo | Empresa | Context | Destacado | |--------|---------|---------|-----------| | **Claude Opus 4.6** | Anthropic | 1M tokens | Autonomous agents | | **GPT-5.2** | OpenAI | 256K tokens | Default en ChatGPT | | **Gemini 3.1 Pro** | Google | N/A | Líder benchmarks | | **Grok 4.2** | xAI | N/A | 4-agent architecture | | **Kimi K2.5** | Moonshot | N/A | Video + agents | --- ## Preguntas Frecuentes ### ¿Por qué OpenAI retiró GPT-4o si era el modelo flagship? OpenAI mantiene una estrategia de "retire fast, iterate faster". Al retirar GPT-4o y modelos antiguos, **fuerzan la adopción** de GPT-5.2 (su modelo más avanzado) y simplifican mantenimiento de infraestructura. Es similar a como Apple descontinúa iPhones viejos para impulsar nuevos modelos. ### ¿Vale la pena el upgrade a Claude Opus 4.6 desde Sonnet? Depende de tu caso de uso. **Opus 4.6** es significativamente más caro pero ofrece **1M context window** y **autonomous agents**. Si trabajas con documentos masivos (legal, research) o necesitas tareas complejas multi-paso, sí. Para coding general y tareas cotidianas, **Sonnet 4.6** es más cost-effective. ### ¿Gemini 3.1 Pro es realmente mejor que GPT-5.2 y Claude Opus 4.6? En **benchmarks específicos**, sí - Gemini 3.1 Pro lidera 13 de 16 tests y tiene el récord en ARC-AGI-2. Pero benchmarks no siempre se traducen a mejor experiencia de usuario. GPT-5.2 tiene mejor **integración** (ChatGPT ecosystem), y Claude tiene mejor **coding** según desarrolladores. La "mejor" IA depende de tu flujo de trabajo. ### ¿Los modelos chinos realmente compiten con OpenAI y Anthropic? **Sí**, especialmente en **generación de video** (Seedance 2.0 rivaliza con Sora) y **cost-effectiveness** (MiniMax M2.5). China está cerrando la brecha rápidamente. Sin embargo, modelos occidentales aún lideran en **reasoning**, **code**, y **safety**. La distancia se está acortando más rápido de lo esperado. ### ¿Qué significa "autonomous agents" en práctica? En lugar de hacer una solicitud y esperar una respuesta, **autonomous agents** dividen tareas complejas en sub-tareas, las ejecutan en paralelo, y sintetizan resultados. Ejemplo: "Analiza estos 50 PDFs y crea un reporte ejecutivo" - un agente tradicional procesa secuencialmente; autonomous agents asignan 5 sub-agentes procesando 10 PDFs cada uno simultáneamente. ### ¿ChatGPT con ads afectará la experiencia? Solo para usuarios **Free** y **Go** en EE.UU. (test). Si pagas Plus, Pro, Business, Enterprise o Education, **no verás ads**. Es el modelo freemium estándar: producto gratuito con ads, pago sin ads. Similar a Spotify, YouTube Premium, etc. --- ## Conclusión Una semana con mucha actividad: Anthropic lanzó 2 modelos en 12 días, OpenAI retiró GPT-4o forzando la adopción de GPT-5.2, Google lideró benchmarks con Gemini 3.1 Pro, xAI redujo alucinaciones un 65% con Grok 4.2, y China sorprendió con Seedance 2.0 y modelos más accesibles en precio. ¿Qué novedad te impactó más? ¿Estás usando alguno de estos modelos? Déjame saber en los comentarios. --- **Fuentes:** - [AI Update February 20, 2026](https://www.marketingprofs.com/opinions/2026/54328/ai-update-february-20-2026-ai-news-and-views-from-the-past-week) - [Claude Sonnet 4.6 Release](https://www.cnbc.com/2026/02/17/anthropic-ai-claude-sonnet-4-6-default-free-pro.html) - [Claude Code Success Story](https://www.bloomberg.com/news/articles/2026-02-20/the-surprise-hit-that-made-anthropic-into-an-ai-juggernaut-mlve4nc2) - [Claude Code Security Launch](https://thehackernews.com/2026/02/anthropic-launches-claude-code-security.html) - [OpenAI Retiring GPT-4o](https://openai.com/index/retiring-gpt-4o-and-older-models/) - [ChatGPT Release Notes](https://releasebot.io/updates/openai/chatgpt) - [Google Gemini Updates](https://9to5google.com/2026/02/21/google-ai-pro-ultra-features/) - [Seedance 2.0 CNN Report](https://www.cnn.com/2026/02/20/china/china-ai-seedance-intl-hnk-dst) - [Tech Corps Announcement](https://www.cnbc.com/2026/02/23/us-launch-peace-corps-tech-corps-india-export-ai-stack-sovereignty-counter-china.html) - [AI Updates February 2026](https://llm-stats.com/llm-updates) --- ### Laravel Semanal: 16-23 Febrero 2026 - URL: https://www.angelcruz.dev/post/laravel-semanal-16-23-febrero-2026 - Markdown: https://www.angelcruz.dev/post/laravel-semanal-16-23-febrero-2026.md - Categoría: Laravel - Fecha: 2026-02-23 - Excerpt: Resumen completo de la semana en Laravel: incidente de Laravel Cloud, AI SDK oficial, Statamic 6, NativePHP gratis, y preparativos para Laravel 13. --- title: "Laravel Semanal: 16-23 Febrero 2026" excerpt: "Resumen completo de la semana en Laravel: incidente de Laravel Cloud, AI SDK oficial, Statamic 6, NativePHP gratis, y preparativos para Laravel 13." date: "2026-02-23T18:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Laravel Semanal 16-23 Feb 2026: AI SDK, Cloud Incident, Statamic 6" seo_description: "Resumen semanal de Laravel: incidente de infraestructura en Laravel Cloud, lanzamiento del AI SDK oficial, Statamic 6 disponible, NativePHP gratis y noticias del ecosistema." keywords: - laravel - laravel semanal - laravel news - laravel ai sdk - laravel cloud - statamic 6 - laravel 13 featured: true --- ![Laravel Weekly Banner](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) Bienvenido al **resumen semanal de Laravel** del 16 al 23 de febrero de 2026. Esta semana estuvo cargada de novedades: desde un incidente de infraestructura que afectó a Laravel Cloud, hasta el lanzamiento del AI SDK oficial y actualizaciones importantes del ecosistema. ## Incidente de Laravel Cloud (20 de Febrero) El **jueves 20 de febrero**, Laravel Cloud experimentó una **interrupción de conectividad** que duró aproximadamente **3 horas y 15 minutos**. La causa fue un incidente en **Cloudflare**, uno de sus socios de infraestructura, que resultó en la retirada de anuncios de prefijos IP que enrutan el tráfico hacia los servicios de Laravel Cloud. No se perdieron datos de clientes, no hubo compromiso de seguridad, y las aplicaciones permanecieron operativas (el problema fue exclusivamente a nivel de red). El equipo publicó un [reporte detallado del incidente](https://laravel.com/blog/laravel-cloud-incident-report-february-20-2026) con el post-mortem completo. --- ## Laravel AI SDK Oficial (5 de Febrero) Laravel anunció su **SDK oficial de IA** el 5 de febrero, que permite integrar funcionalidades de inteligencia artificial directamente en aplicaciones Laravel. ### ¿Qué Puedes Hacer? El [Laravel AI SDK](https://laravel-news.com/laravel-announces-official-ai-sdk-for-building-ai-powered-apps) te permite construir aplicaciones con IA directamente en Laravel, con soporte para: - Generación de texto (ChatGPT, Claude, etc.) - Generación de imágenes (DALL-E, Stable Diffusion) - Generación de audio - Múltiples proveedores: OpenAI, Anthropic, y más ### SDK Unificado Lo más interesante es que proporciona una **API unificada** para trabajar con diferentes proveedores de IA. En lugar de aprender la API de cada proveedor, usas la sintaxis de Laravel: ```php use Laravel\AI\Facades\AI; $response = AI::text() ->using('openai') ->prompt('Explica Eloquent ORM en 3 líneas') ->generate(); ``` Esto simplifica enormemente la integración de IA en tus aplicaciones Laravel. --- ## Markdown for Agents en Laravel Cloud (17 de Febrero) El **17 de febrero**, Laravel Cloud introdujo **"Markdown for Agents"**, una característica que permite servir versiones Markdown de tus páginas cuando son solicitadas por agentes de IA (como ChatGPT, Perplexity, Claude). ### ¿Por Qué es Importante? Los agentes de IA consumen mejor contenido en Markdown que HTML completo. El payload se reduce aproximadamente un 99.6%, lo que mejora la indexación en motores de búsqueda de IA como ChatGPT, Perplexity y Google AI Overviews. [Más información sobre Markdown for Agents](https://laravel-news.com/laravel-cloud-adds-markdown-for-agents-to-serve-ai-friendly-content) --- ## Statamic 6 Lanzado Oficialmente (2 de Febrero) [Statamic 6](https://laravel-news.com/statamic-6-is-officially-released) fue oficialmente lanzado el **2 de febrero de 2026**, trayendo una actualización mayor al CMS construido sobre Laravel. ### Novedades de Statamic 6: - Panel de control rediseñado desde cero - Frontend stack modernizado - Mejor performance y experiencia de usuario - Compatibilidad con Laravel 12 Si estás buscando un CMS headless para Laravel, Statamic 6 es una opción sólida a considerar. --- ## NativePHP for Mobile ahora es gratuito (11 de Febrero) [NativePHP for Mobile](https://laravel-news.com/nativephp-for-mobile-is-now-free) (anteriormente NativePHP Air) pasó a ser **completamente gratuito** desde el 11 de febrero. ### ¿Qué es NativePHP? NativePHP te permite crear aplicaciones **nativas de escritorio y móviles** usando Laravel. Básicamente, construyes tu app con Laravel + Livewire/Vue/React y la empaquetas como app nativa. **Casos de uso:** - Herramientas internas para equipos - Aplicaciones de productividad - Dashboards offline-first - Apps móviles sin aprender Swift/Kotlin --- ## Laravel VS Code Extension v1.5.0 (29 de Enero) La [extensión de VS Code para Laravel](https://laravel-news.com/laravel-vscode-extension-v1-5-0) recibió una actualización con: - Soporte mejorado para Livewire 4 - Mejor autocompletado de componentes Blade - Intellisense para directivas Blade - Syntax highlighting mejorado Si usas VS Code, vale la pena tenerla instalada. --- ## Laravel 13: Preparándose para Marzo 2026 Aunque Laravel 13 no ha sido lanzado aún, ya sabemos bastante sobre la próxima versión mayor: ### Fechas Clave: - Lanzamiento: Marzo 2026 - Requisito mínimo: PHP 8.3 - Bug fixes hasta: Q3 2027 - Security fixes hasta: Q1 2028 ### ¿Qué Esperar? Según [Laravel News](https://laravel-news.com/laravel-13), Laravel 13 continuará la tradición de actualizaciones incrementales sin breaking changes masivos. Se espera: - Dependency upgrades (Symfony 8, PHP 8.3+) - Performance improvements - Developer experience enhancements [Leer más sobre Laravel 13](https://laravel-news.com/laravel-13) --- ## Estado de los Paquetes Laravel en 2026 Según una [encuesta a 200 desarrolladores](https://hackernoon.com/the-state-of-laravel-packages-in-2026-according-to-200-developers), estos son los **paquetes más populares** de 2026: ### Top 5 Paquetes: 1. **Laravel Debugbar** - Debugging esencial 2. **Spatie Laravel Permission** - Roles y permisos elegantes 3. **Laravel Horizon** - Dashboard para Redis queues 4. **Laravel Telescope** - Debugging y profiling avanzado 5. **Spatie Media Library** - Gestión de archivos multimedia ### Nuevos en 2026: - **Laravel AI SDK** (oficial) - **Laravel Boost** - Asistente dev potenciado por IA usando MCP SDK - **Laravel Nightwatch MCP Server** - Diagnóstico de apps con AI agents --- ## Eventos Próximos ### Laravel Live UK 2026 - **Fecha:** 18-19 de Junio, 2026 - **Lugar:** Londres - [Más información](https://laravel-news.com/laravel-live-uk-2026) ### Laravel Live Denmark 2026 - **Fecha:** 20-21 de Agosto, 2026 - **Lugar:** Copenhagen - [Más información](https://laravel-news.com/laravel-live-denmark-returns-to-copenhagen-in-august-2026) --- ## Caso de Éxito: PyleSoft + Laravel Cloud **PyleSoft** migró **6 aplicaciones** de Laravel Vapor a Laravel Cloud y logró: - **50% de reducción** en costos de infraestructura - **300 GB de datos** migrados sin problemas - **1.5 millones de requests diarios** gestionados sin fricción PyleSoft documentó la migración en detalle, incluyendo el proceso de traslado de los 300 GB de datos. --- ## Laravel 12.50.0 - Estado Actual La versión actual de Laravel es **12.50.0** (febrero 2026), que incluye: ### Actualizaciones Recientes: - Compatibilidad con PHP 8.5 (varios paquetes actualizados) - PostgreSQL 18 support con columnas virtuales generadas - Failover queue driver: failover automático si la conexión principal falla - Starter Kits mejorados (React, Vue, Svelte, Livewire) **Soporte:** - Bug fixes hasta: **13 de Agosto, 2026** - Security fixes hasta: **24 de Febrero, 2027** --- ## Recursos de la Semana - [Laravel Official Blog](https://laravel.com/blog) - Anuncios oficiales - [Laravel News](https://laravel-news.com/) - Noticias diarias del ecosistema - [Laravel Daily](https://laraveldaily.com/) - Tips y tutoriales - [Laravel Changelog](https://laravel.com/docs/changelog) - Cambios detallados --- ## Preguntas Frecuentes ### ¿Qué causó el incidente de Laravel Cloud del 20 de febrero? El incidente fue causado por un problema en **Cloudflare** (proveedor de infraestructura) que resultó en la retirada de anuncios de prefijos IP. No hubo pérdida de datos ni compromiso de seguridad - fue únicamente a nivel de red. ### ¿Vale la pena usar el Laravel AI SDK? **Sí**, especialmente si planeas integrar múltiples proveedores de IA. El SDK unificado te ahorra aprender diferentes APIs y facilita cambiar entre proveedores (OpenAI, Anthropic, etc.) sin reescribir código. ### ¿Laravel 13 romperá mi código existente? Laravel mantiene una política de **cambios incrementales sin breaking changes masivos**. La migración de Laravel 12 a 13 debería ser relativamente simple, similar a migraciones previas. El mayor requisito es **PHP 8.3+**. ### ¿Debo migrar de Vapor a Laravel Cloud? Si buscas **reducir costos** (como PyleSoft con 50% de ahorro) o prefieres una plataforma **más integrada** con el ecosistema Laravel, vale la pena considerarlo. Laravel Cloud está madurando rápidamente y ahora ofrece features exclusivas como "Markdown for Agents". ### ¿Qué es NativePHP y para qué sirve? NativePHP te permite crear **aplicaciones nativas de escritorio y móviles** usando Laravel. Es ideal para herramientas internas, dashboards offline-first, o apps de productividad sin necesidad de aprender Swift/Kotlin. ### ¿Statamic 6 es gratuito? Statamic tiene una **versión gratuita** para uso en desarrollo/staging. Para producción necesitas una licencia ($259 por sitio). Es más económico que otros CMS headless enterprise y se integra perfectamente con Laravel. --- ## Conclusión Una semana movida en el ecosistema: el AI SDK oficial, el incidente en Laravel Cloud con su post-mortem completo, NativePHP Mobile gratuito y la extensión de VS Code actualizada. En marzo llega Laravel 13. ¿Qué novedad te interesa más? Déjame saber en los comentarios. --- **Fuentes:** - [Laravel Cloud Incident Report](https://laravel.com/blog/laravel-cloud-incident-report-february-20-2026) - [Laravel AI SDK Announcement](https://laravel-news.com/laravel-announces-official-ai-sdk-for-building-ai-powered-apps) - [Markdown for Agents](https://laravel-news.com/laravel-cloud-adds-markdown-for-agents-to-serve-ai-friendly-content) - [Statamic 6 Release](https://laravel-news.com/statamic-6-is-officially-released) - [NativePHP Free Announcement](https://laravel-news.com/nativephp-for-mobile-is-now-free) - [Laravel 13 Info](https://laravel-news.com/laravel-13) - [Laravel News](https://laravel-news.com/) - [State of Laravel Packages 2026](https://hackernoon.com/the-state-of-laravel-packages-in-2026-according-to-200-developers) --- ### Cómo Instalar OpenClaw 2026: Script, npm, Docker y Raspberry Pi - URL: https://www.angelcruz.dev/post/como-instalar-openclaw-guia-completa - Markdown: https://www.angelcruz.dev/post/como-instalar-openclaw-guia-completa.md - Categoría: OpenClaw - Fecha: 2026-02-16 - Excerpt: Guía completa para instalar OpenClaw (antes Clawdbot) en 2026: script automático en 5 minutos, npm manual, Docker con docker-compose y Raspberry Pi 4/5. Requisito mínimo: Node.js 22+. --- title: "Cómo Instalar OpenClaw 2026: Script, npm, Docker y Raspberry Pi" seo_title: "Cómo Instalar OpenClaw: Docker, Windows y Raspberry Pi" seo_description: "Instala OpenClaw en 5 minutos con script automático (macOS, Linux, Windows WSL2), npm, Docker o Raspberry Pi. Requisito: Node.js 22+. Comandos verificados." excerpt: "Guía completa para instalar OpenClaw (antes Clawdbot) en 2026: script automático en 5 minutos, npm manual, Docker con docker-compose y Raspberry Pi 4/5. Requisito mínimo: Node.js 22+." date: "2026-02-16" lastModified: "2026-03-29T00:00:00.000Z" category: "OpenClaw" author: name: "Angel Cruz" avatar: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/avatar.jpg" url: "https://www.angelcruz.dev" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" --- > **Actualización (febrero 2026):** Este artículo cubre la instalación de **OpenClaw**, que antes se llamó Clawdbot y brevemente Moltbot. Si buscas "Clawdbot instalación", estás en el lugar correcto: es el mismo proyecto. Lee la [historia completa del rebrand](/post/openclaw-de-clawdbot-a-plataforma-agentes-ia) si quieres contexto. Hace unas semanas escribí sobre [Clawdbot (ahora OpenClaw)](/post/clawdbot-asistente-ia-personal-open-source), el asistente de IA open source que promete automatizar tu vida digital desde tu propia máquina. Ese artículo cubrió *qué es* y *por qué es interesante*. Este va directo al grano: **cómo instalarlo paso a paso**, desde el script más simple hasta configuraciones avanzadas con Docker y Raspberry Pi. La historia del nombre es curiosa: el proyecto nació como **Clawdbot** en noviembre de 2025, se renombró a **Moltbot** el 27 de enero de 2026 (por una queja de marca registrada de Anthropic), y tres días después se convirtió en **OpenClaw** porque "Moltbot nunca terminó de sonar bien". El "fastest triple rebrand in open source history", como lo llaman en la comunidad. Si encuentras tutoriales viejos que dicen "Clawdbot" o "Moltbot", tranquilo: es el mismo software, solo cambiaron los nombres de los comandos y dominios. Lo importante: OpenClaw sigue siendo el mismo asistente de IA que puede revisar tus emails, negociar la compra de un auto, generar resúmenes diarios por voz o ejecutar comandos en tu sistema. Y sigue siendo open source, gratuito y completamente privado (corre en tu máquina, no en la nube). ## ¿Qué es OpenClaw? OpenClaw es un asistente de IA personal **open-source** que se ejecuta localmente en tu máquina. A diferencia de asistentes como ChatGPT o Claude que funcionan solo en el navegador, OpenClaw: - Se ejecuta como **proceso en segundo plano** (daemon) en tu sistema - Puede **controlar tu computadora** (abrir apps, ejecutar comandos, navegar) - Se integra con **múltiples canales** (Slack, Discord, Telegram, WhatsApp, Microsoft Teams) - Admite **múltiples modelos de IA** (Claude, GPT-4, modelos locales) - Es totalmente **personalizable** mediante skills y configuración Creado originalmente por **Peter Steinberger** en noviembre de 2025, OpenClaw derivó de Molty (anteriormente Clawd), un asistente virtual de IA que él había desarrollado. **Actualización febrero 2026**: Peter Steinberger anunció el 14 de febrero de 2026 que se unirá a OpenAI, y el proyecto será trasladado a una fundación open-source para garantizar su continuidad. ## Requisitos de OpenClaw Los **requisitos mínimos de OpenClaw** son: - **Node.js 22.19 o superior** (la documentación recomienda Node 24) - Sistema operativo: **macOS**, **Linux**, o **Windows 10/11** (nativo o WSL2) - Al menos **4 GB de RAM** (recomendado 8 GB para uso intensivo) - Cuenta API de un proveedor de IA (Claude, OpenAI o modelo local) ¿Algún error al instalar? Hay una guía aparte de [requisitos y errores comunes](/post/requisitos-y-errores-comunes-openclaw). Y guías dedicadas por plataforma: [Ubuntu](/post/instalar-openclaw-en-ubuntu), [Windows](/post/instalar-openclaw-en-windows), [Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi) y [Docker Compose](/post/instalar-openclaw-con-docker-compose). ## Script OpenClaw: Instalación en 5 Minutos (Recomendado) La forma más rápida de instalar OpenClaw es usando el script de instalación automático: ```bash curl -fsSL https://openclaw.ai/install.sh | bash ``` **¿Qué hace este script?** 1. Detecta tu sistema operativo 2. Instala Node.js 22+ si no está presente 3. Instala OpenClaw globalmente 4. Ejecuta el wizard de configuración (`openclaw onboard`) 5. Instala el daemon/servicio en segundo plano **Ventajas**: - Instalación en menos de 5 minutos - Configura todo automáticamente - Instala el daemon para que OpenClaw esté siempre activo **Después de la instalación**, el wizard te preguntará: 1. **API Key**: Configura Claude, OpenAI, o modelo local 2. **Gateway**: Puerto local para la interfaz web (default: 18789) 3. **Channels**: Slack, Discord, Telegram, etc. 4. **Skills**: Habilidades adicionales (navegación web, ejecución de código, etc.) ## npm install OpenClaw: Instalación Manual en macOS y Linux Si prefieres instalar manualmente con npm: ```bash npm install -g openclaw@latest ``` Luego ejecuta el wizard de configuración: ```bash openclaw onboard --install-daemon ``` **El flag `--install-daemon`**: - En **Linux**: Registra OpenClaw como servicio systemd - En **macOS**: Registra OpenClaw con launchd - Garantiza que el agente sobreviva reinicios del sistema **Verificar instalación**: ```bash openclaw --version // Debería mostrar: v2026.2.14 (o versión más reciente) openclaw status // Muestra si el daemon está activo ``` ## Docker OpenClaw: Instalación Aislada con docker-compose OpenClaw incluye soporte oficial para Docker mediante `docker-compose`. Tengo una [guía dedicada de Docker Compose](/post/instalar-openclaw-con-docker-compose) con el flujo completo; el resumen: ```bash // Clonar el repositorio git clone https://github.com/openclaw/openclaw.git cd openclaw // Usar la imagen oficial (GitHub Container Registry) y correr el setup export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest" ./scripts/docker/setup.sh ``` **Ventajas de Docker**: - **Aislamiento de procesos**: OpenClaw corre en su propio contenedor - **Entornos reproducibles**: Misma configuración en cualquier máquina - **Actualizaciones sin dolor**: `docker-compose pull && docker-compose up -d` - **Bundled runtime**: Incluye Node.js dentro del contenedor **Configuración manual con Docker**: ```bash docker pull ghcr.io/openclaw/openclaw:latest docker run -d \ --name openclaw \ -p 18789:18789 \ -v ~/.openclaw:/home/node/.openclaw \ ghcr.io/openclaw/openclaw:latest ``` **Mapeo de puertos**: - El gateway se mapea a `127.0.0.1:18789` - La configuración se persiste en `~/.openclaw` mediante volúmenes Docker **Acceder al wizard en Docker**: ```bash docker exec -it openclaw openclaw onboard ``` **Docker en servidores VPS**: Docker es especialmente útil si quieres ejecutar OpenClaw en un servidor dedicado (Digital Ocean, AWS, etc.) y acceder a él remotamente. Consulta la [guía oficial de Docker](https://docs.openclaw.ai/install/docker) para configuraciones avanzadas de red y seguridad. ## OpenClaw en Raspberry Pi 4/5: Servidor Dedicado 24/7 OpenClaw puede ejecutarse en una **Raspberry Pi 4 o 5** como servidor dedicado 24/7. El mínimo realista es una **Pi 4 de 4 GB** (puede usar swap bajo carga); una **Pi 5 con 8 GB** va sobrada. Tengo una [guía dedicada paso a paso para Raspberry Pi](/post/instalar-openclaw-en-raspberry-pi) con el setup completo: sistema operativo, daemon y acceso remoto. **Requisitos**: - Raspberry Pi 4 (4GB+ RAM recomendado) o Raspberry Pi 5 (8GB ideal) - Raspberry Pi OS (64-bit recomendado) - Node.js 22+ instalado **Paso 1: Instalar Node.js 22 en Raspberry Pi** ```bash // Usar nvm (Node Version Manager) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash // Cerrar y reabrir terminal, luego: nvm install 22 nvm use 22 // Verificar versión node --version // Debería mostrar: v22.x.x ``` **Paso 2: Instalar OpenClaw** ```bash npm install -g openclaw@latest openclaw onboard --install-daemon ``` **Paso 3: Configurar API Key** Durante el wizard, configura tu API key de Claude o OpenAI. Para modelos **locales** (Llama, Mistral), necesitarás hardware más potente (Pi 5 con 8GB mínimo + USB SSD recomendado). **Paso 4: Acceso remoto** Si quieres acceder a OpenClaw desde otros dispositivos en tu red: ```bash // Editar ~/.openclaw/openclaw.json // Cambiar "host": "127.0.0.1" por "host": "0.0.0.0" // Reiniciar el servicio openclaw restart ``` Ahora puedes acceder desde cualquier dispositivo en tu red local en `http://[IP_DE_TU_PI]:18789` **Raspberry Pi como servidor siempre activo**: - Bajo consumo de energía (~5W) - Ideal para automatizaciones programadas (cron jobs) - Puede ejecutar múltiples agentes simultáneamente con modelos cloud ## Configuración de OpenClaw La configuración de OpenClaw vive en `~/.openclaw/openclaw.json`. Aquí un ejemplo comentado: ```json { "agent": { "model": "claude-sonnet-4.5", "provider": "anthropic", "apiKey": "sk-ant-..." }, "gateway": { "port": 18789, "host": "127.0.0.1" }, "channels": [ { "type": "slack", "token": "xoxb-...", "enabled": true }, { "type": "telegram", "token": "123456:ABC-DEF...", "enabled": true } ], "skills": { "web-browser": true, "code-execution": true, "file-operations": true }, "security": { "allowedCommands": ["git", "npm", "docker"], "blockedCommands": ["rm -rf /", "dd"] } } ``` **Campos importantes**: - **agent.model**: Modelo de IA a usar (claude-sonnet-4.5, gpt-4o, llama-3-70b, etc.) - **gateway.port**: Puerto local para la interfaz web (default: 18789) - **channels**: Canales de mensajería habilitados - **skills**: Habilidades del agente (navegación, código, archivos) - **security**: Comandos permitidos/bloqueados por seguridad ## Integraciones Principales ### Slack ```bash openclaw channel add slack // Sigue las instrucciones para crear una Slack App // Necesitarás un Bot User OAuth Token (xoxb-...) ``` ### Discord ```bash openclaw channel add discord // Proporciona el Discord Bot Token ``` ### Microsoft Teams OpenClaw soporta integración con Microsoft Teams mediante webhooks: ```bash openclaw channel add teams --webhook-url "https://outlook.office.com/webhook/..." ``` **Configuración en Teams**: 1. En tu canal de Teams, ve a "Connectors" 2. Agrega "Incoming Webhook" 3. Copia la URL del webhook 4. Pégala en la configuración de OpenClaw ### Telegram ```bash openclaw channel add telegram // Crea un bot con @BotFather en Telegram // Proporciona el Bot Token ``` ### WhatsApp (Vía Twilio) ```bash openclaw channel add whatsapp --twilio-account-sid "AC..." --twilio-auth-token "..." ``` ### Cron Jobs y Automatizaciones Programadas OpenClaw puede ejecutar tareas automáticamente usando cron jobs: ```bash openclaw skill add cron // Ejemplo: Resumen diario de emails a las 9 AM openclaw cron add "0 9 * * *" "Resume mis emails del día" // Ejemplo: Backup semanal los domingos a las 2 AM openclaw cron add "0 2 * * 0" "Ejecuta backup de la carpeta ~/proyectos" ``` **Formatos de cron**: - `0 9 * * *` = Todos los días a las 9:00 AM - `*/30 * * * *` = Cada 30 minutos - `0 */6 * * *` = Cada 6 horas - `0 2 * * 0` = Domingos a las 2:00 AM ### Voz y ElevenLabs OpenClaw soporta interacción por voz mediante integración con ElevenLabs: ```bash openclaw skill add voice openclaw skill add elevenlabs --api-key "..." ``` **Características de voz**: - **Wake word**: Activa OpenClaw con "Hey OpenClaw" - **Modo always-on**: OpenClaw escucha continuamente - **Text-to-speech**: Respuestas habladas naturales con ElevenLabs - **Modelos de voz**: Elige entre múltiples voces profesionales **Configuración del wake word**: ```bash openclaw config set voice.wakeWord "hey openclaw" openclaw config set voice.alwaysListening true ``` **Requisitos**: - Micrófono funcional - API key de ElevenLabs (plan gratuito disponible) - Conexión a internet estable ### Canvas Interactivo OpenClaw incluye un canvas interactivo para colaboración visual: ```bash openclaw skill add canvas ``` Permite al agente: - Dibujar diagramas y flujos - Editar imágenes - Colaborar en pizarras virtuales - Exportar resultados como PNG/SVG ## Casos de Uso Reales ### 1. Automatización de Desarrollo ```bash // Crear rama, implementar feature, tests, commit, push "Crea una rama llamada 'feature/auth-2fa', implementa autenticación de dos factores, ejecuta los tests, haz commit y push" ``` ### 2. Gestión de Emails ```bash // Revisar emails importantes, responder, archivar "Resume mis emails no leídos de hoy, responde los urgentes y archiva los newsletters" ``` ### 3. Investigación y Síntesis ```bash // Investigar un tema, generar informe "Investiga las últimas tendencias en IA agentic 2026, genera un informe con fuentes" ``` ### 4. Monitoreo de Servers ```bash // Revisar logs, alertar problemas "Revisa los logs del server nginx, alerta si hay errores 5xx en la última hora" ``` ### 5. Scraping y Análisis de Datos ```bash // Extraer datos de sitios web "Extrae las 20 mejores ofertas de trabajo de remote.co para Python developers, guárdalas en CSV" ``` ## Seguridad y Permisos OpenClaw ejecuta comandos en tu sistema, por lo que es importante configurar permisos adecuadamente. **Lista blanca de comandos**: ```json { "security": { "allowedCommands": [ "git", "npm", "docker", "python", "node" ] } } ``` **Lista negra de comandos**: ```json { "security": { "blockedCommands": [ "rm -rf /", "dd", "mkfs", ":(){ :|:& };:" ] } } ``` **Modo sandbox**: ```bash openclaw config set security.sandboxMode true ``` En modo sandbox, OpenClaw solo puede ejecutar comandos en directorios específicos. ## Actualizar OpenClaw ### Con npm: ```bash npm update -g openclaw openclaw restart ``` ### Con Docker: ```bash cd ~/openclaw docker-compose pull docker-compose up -d ``` ### Verificar versión: ```bash openclaw --version ``` ## Solución de Problemas Comunes ### OpenClaw no inicia ```bash // Ver logs openclaw logs // Reiniciar daemon openclaw restart // Verificar que el puerto 18789 no esté ocupado lsof -i :18789 ``` ### Errores de API Key ```bash // Reconfigurar API key openclaw config set agent.apiKey "sk-ant-..." // Verificar provider correcto openclaw config set agent.provider "anthropic" # o "openai" ``` ### Consumo alto de memoria ```bash // Limitar memoria del agente openclaw config set agent.maxMemory "512MB" // Deshabilitar skills no usados openclaw skill disable web-browser ``` ### No responde en canales ```bash // Verificar tokens de channels openclaw channel list // Reconfigurar channel openclaw channel remove slack openclaw channel add slack ``` ## Comparación: OpenClaw vs Zapier vs Make | Característica | OpenClaw | Zapier | Make | |---|---|---|---| | **Ejecución** | Local (tu máquina) | Cloud | Cloud | | **Open-source** | Sí | No | No | | **Costo** | Gratis + API costs | $19.99+/mes | $9+/mes | | **Control total** | Sí | Limitado | Limitado | | **Comandos del sistema** | Sí | No | No | | **Código personalizado** | JavaScript/Python | Limitado | JavaScript | | **Privacidad** | Total (datos locales) | Datos en cloud | Datos en cloud | **Cuándo usar OpenClaw**: - Necesitas control total del sistema - Privacidad de datos es crítica - Quieres automatizaciones complejas sin límites - Prefieres pagar por uso (API calls) vs suscripción mensual **Cuándo usar Zapier/Make**: - Prefieres solución no-code lista para usar - No te importa que tus datos pasen por servidores externos - Necesitas integraciones pre-construidas con cientos de apps ## Recursos Oficiales - **Documentación oficial**: [docs.openclaw.ai](https://docs.openclaw.ai) - **Repositorio GitHub**: [github.com/openclaw/openclaw](https://github.com/openclaw/openclaw) - **Paquete npm**: [npmjs.com/package/openclaw](https://www.npmjs.com/package/openclaw) - **Comunidad Discord**: [discord.gg/openclaw](https://discord.gg/openclaw) ## Preguntas Frecuentes ### ¿OpenClaw es gratis? Sí, OpenClaw es completamente **open-source y gratis**. Solo pagas por los costos de API de tu proveedor de IA (Claude, OpenAI, etc.). Si usas modelos locales (Llama, Mistral), no hay costos de API. ### ¿Puedo usar OpenClaw sin conexión a internet? Sí, si usas modelos de IA **locales** (Llama, Mistral via Ollama). Pero necesitas hardware potente (GPU recomendada) para ejecutar modelos locales. ### ¿OpenClaw funciona en Windows? Sí, mediante **WSL2** (Windows Subsystem for Linux). Instala WSL2 primero, luego sigue las instrucciones de instalación para Linux. ### ¿Cuánta RAM necesita OpenClaw? **Mínimo 4GB**, recomendado **8GB**. Con modelos cloud (Claude, GPT-4), OpenClaw usa ~300MB de RAM. Con modelos locales, necesitas 8-16GB+ dependiendo del modelo. ### ¿Puedo ejecutar múltiples agentes simultáneamente? Sí, OpenClaw soporta múltiples agentes. Cada agente puede tener su propia configuración, modelo de IA y conjunto de skills. ```bash openclaw agent create "agente-email" --model gpt-4o openclaw agent create "agente-dev" --model claude-sonnet-4.5 ``` ### ¿OpenClaw soporta modelos locales? Sí, OpenClaw soporta modelos locales mediante **Ollama** o **llama.cpp**: ```bash // Instalar Ollama curl -fsSL https://ollama.ai/install.sh | sh // Descargar modelo ollama pull llama3:70b // Configurar OpenClaw openclaw config set agent.provider "ollama" openclaw config set agent.model "llama3:70b" ``` ### ¿Es seguro darle acceso a mi sistema? OpenClaw ejecuta comandos con tus permisos de usuario. **Usa listas blancas/negras** de comandos y habilita **modo sandbox** si tienes dudas. Nunca ejecutes OpenClaw como root. ### ¿Puedo contribuir al proyecto? Sí, OpenClaw es open-source. Contribuye en [github.com/openclaw/openclaw](https://github.com/openclaw/openclaw). El proyecto será trasladado a una fundación open-source tras la incorporación de Peter Steinberger a OpenAI. ## Conclusión OpenClaw (anteriormente Clawdbot/Moltbot) es una herramienta poderosa para automatizar tareas, integrar IA en tu flujo de trabajo diario y mantener control total sobre tus datos. Con soporte para múltiples modelos, canales de mensajería, Docker, Raspberry Pi y una comunidad activa, es una alternativa robusta y open-source a servicios cloud como Zapier o Make. **Próximos pasos**: 1. Instala OpenClaw con el método que prefieras 2. Configura tu API key y canales 3. Experimenta con comandos simples 4. Habilita skills avanzados según necesites 5. Únete a la comunidad en Discord **Última actualización**: 16 de febrero de 2026. --- ## Fuentes - [No Mac Mini, no worries: running OpenClaw on a Raspberry Pi | ajfisher](https://ajfisher.me/2026/02/03/openclaw-raspberrypi-howto/) - [OpenClaw Installation and Deployment: Complete Guide | LaoZhang AI Blog](https://blog.laozhang.ai/en/posts/openclaw-installation-deployment-guide) - [Docker - OpenClaw Official Docs](https://docs.openclaw.ai/install/docker) - [Running OpenClaw in Docker | Simon Willison's TILs](https://til.simonwillison.net/llms/openclaw-docker) - [OpenClaw - Wikipedia](https://en.wikipedia.org/wiki/OpenClaw) - [Clawdbot's Final Rename: OpenClaw Officially Released | WenHaoFree](https://blog.wenhaofree.com/en/posts/articles/clawdbots-final-rename-openclaw-officially-released-ending-the-fastest-triple-rebrand-in-open-source-history/) - [The Rise, Fall, and Rebirth of Clawdbot in 72 Hours | EveryDev.ai](https://www.everydev.ai/p/the-rise-fall-and-rebirth-of-clawdbot-in-72-hours) - [openclaw - npm](https://www.npmjs.com/package/openclaw) - [Installer Internals - OpenClaw Official Docs](https://docs.openclaw.ai/install/installer) - [OpenClaw GitHub Repository](https://github.com/openclaw/openclaw) --- ### Cursor Precios 2026: Planes y Costos Actualizados - URL: https://www.angelcruz.dev/post/cursor-ide-precios-planes - Markdown: https://www.angelcruz.dev/post/cursor-ide-precios-planes.md - Categoría: Herramientas - Fecha: 2026-02-14 - Excerpt: Precios actualizados de Cursor IDE directo de la fuente oficial. Comparativa de planes gratuito vs Pro y todos los tiers: Pro+, Ultra, Teams y Enterprise. Última actualización: mayo 2026. --- title: "Cursor Precios 2026: Planes y Costos Actualizados" excerpt: "Precios actualizados de Cursor IDE directo de la fuente oficial. Comparativa de planes gratuito vs Pro y todos los tiers: Pro+, Ultra, Teams y Enterprise. Última actualización: mayo 2026." date: "2026-02-14T22:00:00.000Z" lastModified: "2026-05-30T00:00:00.000Z" category: "Herramientas" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/cursor-og-image.png" seo_title: "Cursor Precios 2026: Free, Pro $20, Pro+ $60, Ultra $200" seo_description: "¿Qué plan de Cursor te conviene? Precios 2026 oficiales: Free, Pro $20, Pro+ $60, Ultra $200, Teams $40. Cómo funciona el uso por dólares y cuál elegir." --- **Cursor tiene 6 planes en 2026:** Free ($0), Pro ($20/mes), Pro+ ($60/mes), Ultra ($200/mes), Teams ($40/usuario) y Enterprise (personalizado). Los planes pagos no se diferencian por un número de "requests", sino por el **uso de API incluido cada mes, medido en dólares**. Abajo desgloso cada uno: qué incluye, cuánto cuesta y a quién le conviene. Todos los precios vienen de la [página oficial de Cursor](https://cursor.com/pricing) y de su [documentación de modelos y precios](https://cursor.com/docs/models-and-pricing), no de blogs de terceros. ## Precios de Cursor Cursor ofrece **6 planes** para 2026, desde un plan gratuito hasta Enterprise personalizado. La diferencia clave entre los planes pagos no es un número de "requests", sino la cantidad de **uso de API incluido (medido en dólares)** cada mes: | Plan | Precio Mensual | Uso de API incluido | Ideal Para | |------|---------------|---------------------|------------| | **Hobby (Free)** | $0 | Limitado (modelo Auto) | Probar Cursor | | **Pro** | $20/mes | $20 de uso | Desarrolladores individuales | | **Pro+** | $60/mes | $70 de uso | Power users | | **Ultra** | $200/mes | $400 de uso | Heavy users | | **Teams** | $40/usuario/mes | $20 de uso por usuario | Equipos de 3+ personas | | **Enterprise** | Custom | Personalizado (pooled) | Empresas grandes | > **Importante:** En **junio de 2025** Cursor cambió su modelo de facturación. Dejó atrás el sistema de "requests" con límite fijo y pasó a un **modelo basado en uso medido en dólares**: cada plan incluye un monto de uso de API, y consumes ese monto según los tokens y el modelo que utilices. Cursor lo explica en su post oficial [Clarifying our pricing](https://cursor.com/blog/june-2025-pricing). Todos los planes pagos (Pro, Pro+, Ultra, Teams, Enterprise) incluyen lo mismo de base: **tab completions ilimitados**, límites extendidos de agente en todos los modelos, acceso a **Bugbot** y a los **Cloud Agents**. ## Cómo funciona el uso (el detalle que casi nadie explica bien) Cursor mantiene **dos pools de uso mensual** separados ([docs oficiales](https://cursor.com/docs/models-and-pricing)): 1. **Pool Auto + Composer:** ofrece bastante más uso incluido a tarifas más bajas. Es el que usas para el trabajo diario con el router **Auto** (Cursor elige el modelo por ti). Las tarifas Auto son fijas: **$1.25 por 1M de tokens de entrada, $6.00 por 1M de salida y $0.25 por 1M de cache read**, sin importar el modelo que termine usando. 2. **Pool de API:** cuando eliges un modelo premium manualmente, se cobra a la **tarifa estándar de API de ese modelo**. El precio por tokens depende del modelo: - **Tokens de entrada:** entre $1.25 y $5 por millón. - **Tokens de salida:** entre $2.5 y $30 por millón. - **Cache read:** entre $0.02 y $0.6 por millón. Cuando agotas tu uso incluido del mes puedes **seguir con pago por uso (on-demand)** a las mismas tarifas de API, o subir de plan. Cursor aclara que **las respuestas nunca se degradan** en calidad ni velocidad por estar en overage. ## Plan gratuito vs Pro: comparativa rápida La decisión más común es entre **Free (Hobby)** y **Pro**: | Característica | Free (Hobby) | Pro ($20/mes) | |----------------|-------------|---------------| | **Precio** | $0 | $20/mes | | **Uso de API incluido** | Limitado | $20/mes | | **Tab completions** | Limitado | Ilimitado | | **Modelos** | Solo router Auto | Auto + modelos frontier completos | | **Cloud Agents** | No | Sí | | **Bugbot** | No | Sí | | **Tarjeta de crédito** | No requerida | Requerida | **Veredicto:** si usas Cursor más de una hora al día para trabajo real, el plan Pro a $20/mes se paga solo en tiempo ahorrado. ## Plan Hobby (Free): ¿es suficiente? El plan gratuito te deja probar el editor sin compromiso: - Acceso a **Agent, Chat y Tab completions con el modelo Auto**. - Uso de API **limitado** y tab completions limitados. - **Sin tarjeta de crédito**. Es ideal si quieres probar Cursor antes de comprometerte, si usas IA de forma muy ocasional o si estás aprendiendo a programar. La limitación práctica: si desarrollas a tiempo completo, probablemente agotes el uso incluido en pocos días. ## Plan Pro: el más popular ($20/mes) El plan **Pro** es el recomendado para desarrolladores individuales: - **$20 de uso de API incluido** cada mes. - **Tab completions ilimitados**. - Límites extendidos de agente en **todos los modelos** (incluidos los frontier). - **Cloud Agents** y **Bugbot** incluidos. - Posibilidad de seguir con pago por uso al agotar el monto incluido. Es ideal si desarrollas profesionalmente, usas IA para código a diario y quieres acceso a los modelos frontier sin preocuparte por el autocompletado. Para la mayoría de developers individuales, Pro es el punto óptimo de precio y valor. ## Plan Pro+: para power users ($60/mes) El plan **Pro+** sube el uso incluido de $20 a **$70 al mes** (más del triple) por $60: - **$70 de uso de API incluido**. - Todo lo del plan Pro. Cursor lo recomienda para quienes usan el agente a diario y agotan el uso de Pro de forma recurrente. Si consumes más de $20 de uso al mes pero menos de $70, Pro+ te sale más barato que Pro + overage. ## Plan Ultra: para heavy users ($200/mes) El plan **Ultra** es el tier más alto para individuales: - **$400 de uso de API incluido** (20x el de Pro). - Acceso anticipado a nuevas features. - Todo lo del plan Pro+. Cursor lo posiciona para "agent power users": gente que hace pair programming con IA prácticamente todo el día. La realidad es que la mayoría de developers **no** necesitan Ultra; solo tiene sentido si el costo de $200/mes es menor que el valor del tiempo que te ahorra. Cursor detalla los cambios de este tier en [Updates to Ultra and Pro](https://cursor.com/blog/new-tier). ## Plan Teams: para equipos ($40/usuario/mes) El plan **Teams** añade administración, seguridad y colaboración: - **$20 de uso de API incluido por usuario**. - **Privacy mode** a nivel de todo el equipo (tus datos no se usan para entrenar modelos). - **SSO SAML/OIDC** y administración centralizada. - **Facturación unificada** del equipo. - **Code reviews con Bugbot**, marketplace de reglas/skills/plugins del equipo y analítica de uso. Vale la pena si tienes un equipo de 3+ personas y necesitas privacy mode, SSO o controles administrativos. Si solo quieren el uso incluido, cada uno con su Pro individual puede salir más económico. ## Plan Enterprise: personalizado El plan **Enterprise** se ajusta a la organización: - **Uso compartido (pooled)** para optimizar el presupuesto entre todos los asientos. - **Facturación por invoice/PO** y **gestión de asientos con SCIM**. - Controles de acceso a repositorios, modelos y MCP; controles de auto-run, browser y red. - **Audit logs**, service accounts y **API de tracking de código IA**. - **Soporte y account management prioritarios**. Para cotización hay que contactar a ventas desde [cursor.com/pricing](https://cursor.com/pricing). ## ¿Qué plan elegir? - **Empiezas con Cursor → Free.** Pruébalo una o dos semanas. Si agotas el uso incluido, pasa a Pro. - **Desarrollador individual → Pro ($20/mes).** El sweet spot de precio y valor para uso diario. - **Usas el agente a diario y agotas Pro → Pro+ ($60/mes).** $70 de uso incluido en vez de $20. - **IA intensiva todo el día → Ultra ($200/mes).** Solo si realmente lo necesitas; $400 de uso incluido. - **Equipo de 3+ personas → Teams ($40/usuario).** Por privacy mode, SSO y administración. - **Empresa grande → Enterprise.** Para uso pooled, compliance y soporte dedicado. ## Facturación anual Cursor ofrece facturación anual con descuento en sus planes pagos. El monto exacto del descuento puede cambiar, así que conviene verificarlo en el momento de la compra directamente en [cursor.com/pricing](https://cursor.com/pricing) en lugar de fiarse de tablas de terceros que suelen quedar desactualizadas. ## FAQ sobre precios de Cursor ### ¿Cómo se mide el "uso" en Cursor? En dólares. Cada plan incluye un monto de uso de API ($20 en Pro, $70 en Pro+, $400 en Ultra) que se consume según los tokens y el modelo que uses. No es un conteo fijo de "requests". ### ¿Qué pasa si agoto el uso incluido? Puedes activar pago por uso (on-demand) y seguir a las mismas tarifas de API, o subir de plan. Las respuestas no se degradan en calidad ni velocidad. ### ¿El uso incluido se acumula mes a mes? No. El monto incluido se renueva cada ciclo y no se acumula. ### ¿Puedo cambiar de plan cuando quiera? Sí. Puedes subir o bajar de plan; los cambios de upgrade se prorratean y los downgrades aplican al siguiente ciclo. ### ¿Qué modelos están incluidos? Los planes pagos dan acceso a los modelos frontier disponibles (de Anthropic, OpenAI, Google y otros) vía el router **Auto** o seleccionándolos manualmente. El plan Free queda limitado al modelo Auto. ### ¿Puedo usar mi propia API key? Cursor gestiona el acceso a los modelos internamente dentro de su modelo de uso. Si necesitas usar tus propias keys, la alternativa es VS Code con extensiones. ## Alternativas a Cursor Si el precio de Cursor no encaja con tu presupuesto, considera: - **GitHub Copilot:** plan más económico, menos potente en flujos de agente. - **Windsurf:** competidor directo con enfoque agentico. - **Continue.dev:** open source, usas tus propias API keys. - **Cline / Roo Code:** extensiones open source para VS Code con bring-your-own-key. ### Herramientas complementarias Independientemente del editor, estas herramientas mejoran tu flujo con IA: - **[Context7](/post/context7-documentacion-actualizada-asistentes-codigo-ia)**: documentación actualizada de +1000 librerías vía MCP (compatible con Cursor). - **[OpenClaw](/post/clawdbot-asistente-ia-personal-open-source)**: asistente IA personal open source para automatización avanzada. ## Conclusión **Cursor Pro ($20/mes)** es el plan recomendado para la mayoría de desarrolladores profesionales: $20 de uso incluido, tab completions ilimitados y acceso a modelos frontier. **Pro+ ($60/mes)** tiene sentido si usas el agente a diario y agotas el uso de Pro. **Ultra ($200/mes)** es overkill salvo que uses IA de forma intensiva todo el día. **Teams ($40/usuario)** vale la pena para equipos que necesitan privacy mode, SSO o administración centralizada. Mi recomendación: empieza en Free, pasa a Pro si lo usas a diario, y sube a Pro+ o Ultra solo cuando demuestres que agotas el uso de forma recurrente. --- ## Fuentes - [Cursor · Pricing](https://cursor.com/pricing) (página oficial) - [Models & Pricing | Cursor Docs](https://cursor.com/docs/models-and-pricing) (documentación oficial) - [Clarifying our pricing](https://cursor.com/blog/june-2025-pricing) (blog oficial de Cursor) - [Updates to Ultra and Pro](https://cursor.com/blog/new-tier) (blog oficial de Cursor) --- ### OpenClaw vs Zapier: Cuál Elegir para Automatización - URL: https://www.angelcruz.dev/post/openclaw-vs-zapier-cual-elegir-automatizacion - Markdown: https://www.angelcruz.dev/post/openclaw-vs-zapier-cual-elegir-automatizacion.md - Categoría: OpenClaw - Fecha: 2026-02-14 - Excerpt: Comparativa completa entre OpenClaw (antes OpenClaw) y Zapier: diferencias clave, pricing, capacidades de automatización y cuándo usar cada herramienta según tus necesidades. --- title: "OpenClaw vs Zapier: Cuál Elegir para Automatización" excerpt: "Comparativa completa entre OpenClaw (antes OpenClaw) y Zapier: diferencias clave, pricing, capacidades de automatización y cuándo usar cada herramienta según tus necesidades." date: "2026-02-14T20:30:00.000Z" category: "OpenClaw" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" seo_title: "OpenClaw vs Zapier: Cuál Elegir para Automatización" seo_description: "Comparativa OpenClaw (antes OpenClaw) vs Zapier: pricing, features, complejidad y casos de uso. Zapier desde $19.99/mes para integraciones simples, OpenClaw gratis para automatización avanzada y privacidad. Actualizado febrero 2026." --- Si estás buscando automatizar tareas repetitivas, probablemente hayas escuchado sobre **Zapier** (la plataforma cloud líder en integraciones) y **OpenClaw** (el asistente de IA open source que se ejecuta localmente y permite automatización avanzada con control total de tus datos). Aunque ambos prometen ahorrarte tiempo, funcionan de formas completamente diferentes. En este artículo, compararemos ambas herramientas para que puedas decidir cuál se adapta mejor a tus necesidades. ## ¿Qué es cada herramienta? ### Zapier: Automatización de workflows en la nube [Zapier](https://zapier.com) es una plataforma cloud que conecta más de 8,000 aplicaciones y automatiza workflows sin necesidad de código. Funciona con "Zaps" (automatizaciones) que siguen la lógica: **Trigger → Action**. **Ejemplo:** Cuando llega un email a Gmail (trigger) → crear tarea en Todoist (action). Zapier es ideal para conectar aplicaciones SaaS entre sí de forma rápida y sin configuración técnica compleja. ### OpenClaw (ahora OpenClaw): Asistente IA autónomo y self-hosted **OpenClaw** (renombrado en enero 2026) es un asistente de IA open source que vive en tus apps de mensajería (WhatsApp, Telegram, Slack) y se ejecuta localmente en tu dispositivo. No solo conecta apps: **ejecuta tareas reales** en tu sistema operativo, navega la web, escribe código y puede actuar de forma proactiva. Si quieres probarlo, primero necesitas [instalar OpenClaw](/post/como-instalar-openclaw-guia-completa) en tu equipo. **Ejemplo:** Le dices por WhatsApp "compra un auto usado en mi zona" → OpenClaw busca inventarios, contacta vendedores, negocia precios y te reporta resultados. ## Comparativa: OpenClaw vs Zapier | Factor | OpenClaw (OpenClaw) | Zapier | |--------|---------------------|--------| | **Tipo** | Asistente IA autónomo | Plataforma de integración cloud | | **Hosting** | Self-hosted (Raspberry Pi, Mac, VPS) | Cloud (SaaS) | | **Pricing** | Gratis (solo pagas API LLM ~$10-20/mes) | Desde $19.99/mes (750 tasks) | | **Setup inicial** | 10-15 min técnico (terminal) | <5 min (interfaz visual) | | **Integraciones** | +50 nativas + infinitas custom | +8,000 apps pre-built | | **Complejidad workflows** | Ilimitada (código, IA, autonomía) | Limitada (2-step en Free, multi-step en Pro) | | **Privacidad datos** | Total (local) | Cloud (datos en servidores Zapier) | | **Proactividad** | Sí (puede iniciar conversación) | No (solo react a triggers) | | **Memoria persistente** | Sí (recuerda semanas/meses) | No (cada Zap es stateless) | | **Ejecución código** | Sí (terminal, scripts, navegación web) | No (solo transferencia de datos) | | **Curva aprendizaje** | Alta (requiere skills técnicos) | Baja (no-code friendly) | | **Ideal para** | Developers, power users, privacidad | No-coders, equipos, integraciones rápidas | ## Diferencias clave ### 1. Tipo de automatización **Zapier** se enfoca en **workflows predefinidos** entre apps SaaS. Es perfecto para: - "Cuando recibo email con X → guardar en Google Sheets" - "Cuando se crea tarea en Asana → notificar en Slack" - "Cuando hay venta en Stripe → enviar email de bienvenida" **OpenClaw** hace **automatización autónoma con IA**. Puede: - Entender contexto de conversaciones pasadas - Tomar decisiones complejas sin reglas fijas - Ejecutar comandos en tu sistema (crear archivos, correr scripts) - Navegar websites y extraer información - Actuar proactivamente (ej: "te aviso si hay email urgente") ### 2. Hosting y privacidad **Zapier:** Tus datos pasan por servidores de Zapier. Funciona 100% en la nube, sin necesidad de gestionar infraestructura. **OpenClaw:** Se ejecuta en tu propio dispositivo (Mac, Raspberry Pi, VPS). Tus datos nunca salen de tu control. Ideal si trabajas con información sensible o quieres soberanía total de datos. ### 3. Pricing y costos **Zapier ([source](https://www.activepieces.com/blog/zapier-pricing)):** - **Free:** 100 tasks/mes, solo Zaps de 2-steps - **Professional:** $19.99/mes (750 tasks/mes), multi-step Zaps, premium apps - **Team:** $69/mes (colaboración, permisos, SSO) - **Enterprise:** Custom pricing (tareas ilimitadas, soporte avanzado) Si alcanzas tu límite, Zapier cobra 1.25x por task adicional ([source](https://www.lindy.ai/blog/zapier-pricing)). **OpenClaw ([source](https://clawd.bot)):** - **Software:** Gratis (open source) - **Costos:** Solo pagas por APIs de LLM que uses (Claude, GPT-4) - Claude Opus: ~$15-20/mes de uso promedio - GPT-4: ~$10-15/mes - Modelos locales: $0 (pero requieren GPU potente) ### 4. Complejidad de setup **Zapier:** No-code completo. Interfaz visual drag-and-drop. Puedes crear tu primer Zap en menos de 5 minutos sin tocar terminal ([source](https://www.getmagical.com/blog/zapier-pricing)). **OpenClaw:** Requiere terminal, Node.js, y configuración manual de APIs. Setup inicial toma ~2 horas para usuarios técnicos ([source](https://www.chatprd.ai/how-i-ai/24-hours-with-clawdbot-moltbot-3-workflows-for-ai-agent)). **No es para principiantes sin experiencia técnica.** ### 5. Capacidades técnicas **Zapier:** - Limitado a transferir datos entre apps - No puede ejecutar código arbitrario - No tiene memoria entre ejecuciones - Workflows siguen lógica fija (si X → entonces Y) **OpenClaw ([source](https://www.macstories.net/stories/clawdbot-showed-me-what-the-future-of-personal-ai-assistants-looks-like/)):** - Ejecuta comandos shell en tu sistema - Navega websites con Playwright (browser automation) - Puede escribir código, crear PRs en GitHub - Memoria persistente de conversaciones - Toma decisiones autónomas con IA (no solo reglas fijas) ## Casos de uso: Cuándo usar cada uno ### Usa Zapier si: - Necesitas conectar apps SaaS populares (Gmail, Slack, Trello, Salesforce) - Quieres setup rápido sin tocar terminal - Trabajas en equipo y necesitas colaboración - No tienes skills técnicos (no-code) - Tus workflows son simples y predecibles - Prefieres pagar mensual fijo y olvidarte de infraestructura **Ejemplos:** - "Cada venta en Shopify → agregar cliente a Mailchimp" - "Nuevo archivo en Dropbox → notificar en Slack" - "Tweet con #keyword → guardar en Google Sheets" ### Usa OpenClaw si: - Eres developer o power user con skills técnicos - Necesitas **privacidad total** (datos sensibles, compliance) - Quieres automatización **autónoma con IA** (no solo reglas fijas) - Necesitas ejecutar código o comandos en tu sistema - Buscas **alternativa open source gratuita** a herramientas pagas - Quieres control total de costos (solo pagas APIs) **Ejemplos:** - "Revisar inbox, responder emails urgentes automáticamente" - "Crear resumen diario de noticias + calendario + emails, enviarme por voz" - "Monitorear precio de auto usado, negociar con vendedores" - "Analizar logs de servidor, alertarme de errores críticos" ## Alternativas y combinaciones ### ¿Y si necesitas ambos? Puedes usar **Zapier para integraciones rápidas SaaS** y **OpenClaw para automatización avanzada local**. No son mutuamente excluyentes. **Ejemplo:** Zapier conecta tus apps cloud (Stripe → Mailchimp), mientras OpenClaw maneja tareas complejas en tu sistema (revisar inbox, generar reportes, navegar webs). ### Otras alternativas ([source](https://blog.saner.ai/best-openclaw-alternatives/)) Si ninguna de las dos te convence: - **Make (Integromat):** Similar a Zapier pero con workflows más complejos y mejor pricing - **n8n:** Open source como OpenClaw, pero enfocado en integraciones (no IA) - **Activepieces:** Alternativa open source a Zapier con self-hosting ## Conclusión **Zapier** es la mejor opción si buscas **simplicidad, rapidez y cero configuración técnica**. Perfecto para equipos no-técnicos que necesitan conectar apps cloud rápidamente. **OpenClaw** brilla si eres **developer**, valoras **privacidad extrema**, o necesitas **automatización autónoma con IA** que va más allá de simples transferencias de datos. **Mi recomendación:** - **Emprendedor/equipo no-técnico → Zapier** (empieza con Free, upgrade a Pro si necesitas) - **Developer/power user → OpenClaw** (especialmente si trabajas con datos sensibles) - **Startup técnica → Ambos** (Zapier para SaaS, OpenClaw para tareas avanzadas) ¿Quieres probar OpenClaw? Lee nuestra [guía completa de instalación](/post/clawdbot-asistente-ia-personal-open-source). --- ## Fuentes - [Zapier Pricing 2026](https://www.activepieces.com/blog/zapier-pricing) - Activepieces - [Zapier Pricing Plans](https://www.lindy.ai/blog/zapier-pricing) - Lindy AI - [OpenClaw Official Site](https://openclaw.ai) - OpenClaw (formerly OpenClaw) - [OpenClaw Review](https://www.macstories.net/stories/clawdbot-showed-me-what-the-future-of-personal-ai-assistants-looks-like/) - MacStories - [24 Hours with OpenClaw](https://www.chatprd.ai/how-i-ai/24-hours-with-clawdbot-moltbot-3-workflows-for-ai-agent) - ChatPRD - [Best OpenClaw Alternatives](https://blog.saner.ai/best-openclaw-alternatives/) - Saner AI --- ### Aprende Laravel: Proyecto Práctico - Blog Simple - URL: https://www.angelcruz.dev/post/aprende-laravel-proyecto-blog - Markdown: https://www.angelcruz.dev/post/aprende-laravel-proyecto-blog.md - Categoría: Laravel - Fecha: 2026-02-14 - Excerpt: Construye tu primer proyecto Laravel desde cero: un blog completo con autenticación, CRUD de posts y comentarios. Proyecto final de la serie. --- title: "Aprende Laravel: Proyecto Práctico - Blog Simple" excerpt: "Construye tu primer proyecto Laravel desde cero: un blog completo con autenticación, CRUD de posts y comentarios. Proyecto final de la serie." date: "2026-02-14T12:00:00.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Cómo Crear un Blog en Laravel 13: Proyecto Práctico Paso a Paso" seo_description: "Aprende a crear un blog completo con Laravel 13: autenticación, CRUD, relaciones, validación y deploy. Proyecto práctico paso a paso." keywords: - laravel - proyecto laravel - blog laravel - crud laravel - laravel tutorial - laravel español learning_path: series: "laravel-fundamentals" order: 6 total: 6 prev_slug: "aprende-laravel-models-database" --- ![Laravel Blog Project](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) Has llegado al final de la serie. En los 5 posts anteriores aprendiste [instalación](/post/aprende-laravel-instalacion-setup), [rutas](/post/aprende-laravel-rutas), [vistas](/post/aprende-laravel-vistas-layouts), [controllers](/post/aprende-laravel-controllers) y [models](/post/aprende-laravel-models-database). Ahora vamos a aplicar TODO ese conocimiento construyendo un **blog completo desde cero**. ### ¿Qué vamos a construir? Un blog funcional con estas features: - Autenticación de usuarios (login/registro) - CRUD completo de posts (crear, leer, actualizar, eliminar) - Sistema de comentarios - Autorización (solo el autor puede editar su post) - Búsqueda de posts - Paginación - Estados: Published/Draft ### Stack Tecnológico - **Laravel 13**: Framework principal - **Blade**: Motor de plantillas - **Breeze**: Kit de autenticación - **SQLite**: Base de datos (fácil para desarrollo) - **Tailwind CSS**: Styling (incluido con Breeze) ### Paso 1: Crear el Proyecto Abre tu terminal y ejecuta: ```bash laravel new blog-simple --no-starter-kit cd blog-simple ``` El flag `--no-starter-kit` evita el wizard interactivo del instalador de Laravel 13. Instalamos Breeze manualmente en el Paso 3. O usando Composer (también válido): ```bash composer create-project laravel/laravel blog-simple cd blog-simple ``` ### Paso 2: Configurar la Base de Datos Para este tutorial usaremos **SQLite** (no requiere instalación): ```bash touch database/database.sqlite ``` Edita `.env`: ```bash DB_CONNECTION=sqlite // Comenta o elimina estas líneas: // DB_HOST=127.0.0.1 // DB_PORT=3306 // DB_DATABASE=laravel ``` Ejecuta las migrations iniciales: ```bash php artisan migrate ``` > **SQLite es perfecto para desarrollo y prototipos.** Para producción, considera MySQL o PostgreSQL. ### Paso 3: Instalar Laravel Breeze **Laravel Breeze** provee autenticación lista para usar (login, registro, recuperación de contraseña): ```bash composer require laravel/breeze --dev php artisan breeze:install blade ``` Te preguntará si quieres dark mode y testing. Responde según prefieras: ```bash Would you like dark mode support? (yes/no) [no]: > no Would you like to install Pest for testing? (yes/no) [yes]: > yes ``` Instala las dependencias de npm y compila assets: ```bash npm install npm run dev ``` Ejecuta las nuevas migrations de Breeze: ```bash php artisan migrate ``` Levanta el servidor: ```bash php artisan serve ``` Visita `http://127.0.0.1:8000` y verás links de **Login** y **Register** en el header. ### Paso 4: Crear el Model Post con Migration ```bash php artisan make:model Post -m ``` Edita la migration (`database/migrations/xxxx_create_posts_table.php`): ```php public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('title'); $table->text('content'); $table->boolean('published')->default(false); $table->timestamp('published_at')->nullable(); $table->timestamps(); }); } ``` Ejecuta la migration: ```bash php artisan migrate ``` ### Paso 5: Configurar el Model Post Edita `app/Models/Post.php`: ```php 'boolean', 'published_at' => 'datetime', ]; // Relación: Un post pertenece a un usuario public function user(): BelongsTo { return $this->belongsTo(User::class); } } ``` Y actualiza `app/Models/User.php`: ```php use Illuminate\Database\Eloquent\Relations\HasMany; public function posts(): HasMany { return $this->hasMany(Post::class); } ``` ### Paso 6: Crear el PostController ```bash php artisan make:controller PostController --resource ``` Edita `app/Http/Controllers/PostController.php`: ```php middleware('auth')->except(['index', 'show']); } public function index() { $posts = Post::with('user') ->where('published', true) ->latest() ->paginate(10); return view('posts.index', compact('posts')); } public function create() { return view('posts.create'); } public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', 'published' => 'boolean', ]); $post = Auth::user()->posts()->create([ 'title' => $validated['title'], 'content' => $validated['content'], 'published' => $request->has('published'), 'published_at' => $request->has('published') ? now() : null, ]); return redirect() ->route('posts.show', $post) ->with('success', 'Post creado exitosamente!'); } public function show(Post $post) { // Solo mostrar posts publicados o del autor if (!$post->published && $post->user_id !== Auth::id()) { abort(404); } return view('posts.show', compact('post')); } public function edit(Post $post) { // Autorización: solo el autor puede editar if ($post->user_id !== Auth::id()) { abort(403); } return view('posts.edit', compact('post')); } public function update(Request $request, Post $post) { if ($post->user_id !== Auth::id()) { abort(403); } $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', 'published' => 'boolean', ]); $post->update([ 'title' => $validated['title'], 'content' => $validated['content'], 'published' => $request->has('published'), 'published_at' => $request->has('published') ? ($post->published_at ?? now()) : null, ]); return redirect() ->route('posts.show', $post) ->with('success', 'Post actualizado!'); } public function destroy(Post $post) { if ($post->user_id !== Auth::id()) { abort(403); } $post->delete(); return redirect() ->route('posts.index') ->with('success', 'Post eliminado!'); } } ``` > **Route Model Binding en acción:** Laravel automáticamente busca el Post por ID en la URL. Si no existe, retorna 404. ### Paso 7: Definir las Rutas Edita `routes/web.php`: ```php route('posts.index'); }); Route::resource('posts', PostController::class); require __DIR__.'/auth.php'; ``` ### Paso 8: Crear las Vistas **Layout principal** (`resources/views/layouts/app.blade.php`) ya viene con Breeze. **Vista Index** - Crea `resources/views/posts/index.blade.php`: ```blade

Blog Posts

@auth Nuevo Post @endauth
@if (session('success'))
{{ session('success') }}
@endif
@forelse($posts as $post) @empty

No hay posts publicados aún.

@endforelse
{{ $posts->links() }}
``` **Vista Create** - Crea `resources/views/posts/create.blade.php`: ```blade

Crear Nuevo Post

@csrf
@error('title')

{{ $message }}

@enderror
@error('content')

{{ $message }}

@enderror
Cancelar
``` **Vista Show** - Crea `resources/views/posts/show.blade.php`: ```blade

{{ $post->title }}

@can('update', $post)
Editar
@csrf @method('DELETE')
@endcan
@if (session('success'))
{{ session('success') }}
@endif

Por {{ $post->user->name }} • {{ $post->published_at?->format('d/m/Y') ?? 'Borrador' }}

{!! nl2br(e($post->content)) !!}
``` **Vista Edit** - Crea `resources/views/posts/edit.blade.php`: ```blade

Editar Post

@csrf @method('PUT')
@error('title')

{{ $message }}

@enderror
@error('content')

{{ $message }}

@enderror
Cancelar
``` ### Paso 9: Crear una Policy para Autorización ```bash php artisan make:policy PostPolicy --model=Post ``` Edita `app/Policies/PostPolicy.php`: ```php id === $post->user_id; } public function delete(User $user, Post $post): bool { return $user->id === $post->user_id; } } ``` > **Las Policies centralizan la lógica de autorización.** Usa @can en Blade y $this->authorize() en controllers para aplicarlas. ### Paso 10: Probar la Aplicación 1. **Registra un usuario**: Ve a `/register` 2. **Crea un post**: Click en "Nuevo Post" 3. **Publica el post**: Marca el checkbox "Publicar" 4. **Edita tu post**: Solo tú verás el botón "Editar" 5. **Cierra sesión** y verás que el post sigue visible pero no puedes editarlo ### Características Adicionales (Opcionales) **1. Búsqueda de Posts** Agrega un método `search` al controller: ```php public function index(Request $request) { $query = Post::with('user')->where('published', true); if ($search = $request->input('search')) { $query->where(function($q) use ($search) { $q->where('title', 'like', "%{$search}%") ->orWhere('content', 'like', "%{$search}%"); }); } $posts = $query->latest()->paginate(10); return view('posts.index', compact('posts')); } ``` Y agrega un formulario de búsqueda en `index.blade.php`. **2. Comentarios** Crea un model Comment con relación a Post y User. **3. Categorías** Crea un model Category con relación Many-to-Many con Post. ### Deploy a Producción **Opciones de hosting:** 1. **Laravel Forge** - Fácil, automatizado, desde $12/mes 2. **Laravel Cloud** - Plataforma oficial de Laravel, optimizada para Laravel 3. **Railway** - Deploy con Git, gratis hasta cierto uso 4. **DigitalOcean** - VPS desde $6/mes **Pasos básicos para deploy:** ```bash // 1. Configurar .env de producción APP_ENV=production APP_DEBUG=false APP_URL=https://tublog.com // 2. Optimizar php artisan config:cache php artisan route:cache php artisan view:cache // 3. Migrar database php artisan migrate --force // 4. Generar APP_KEY si no existe php artisan key:generate ``` > **NUNCA hagas commit de tu archivo .env!** Cada entorno debe tener su propio .env con credenciales únicas. ### Next Steps: Expande tu Blog Ahora que tienes un blog funcional, puedes agregar: - **Rich Text Editor**: Integra TinyMCE o Trix - **Image Uploads**: Usa Laravel Storage - **Tags**: Sistema de etiquetas con Many-to-Many - **RSS Feed**: Para suscriptores - **API**: Exponer posts vía JSON API - **Testing**: Escribe tests con Pest/PHPUnit - **Emails**: Notificaciones de nuevos comentarios - **Admin Dashboard**: Panel con estadísticas ### Conclusión de la Serie Felicidades por completar la serie **Aprende Laravel Fundamentals**. Has aprendido: 1. [Instalación y Setup](/post/aprende-laravel-instalacion-setup) 2. [Rutas](/post/aprende-laravel-rutas) 3. [Vistas y Layouts](/post/aprende-laravel-vistas-layouts) 4. [Controllers](/post/aprende-laravel-controllers) 5. [Models y Database](/post/aprende-laravel-models-database) 6. **Proyecto Práctico** (este post) Ahora estás listo para explorar temas más avanzados como: - **Testing** con Pest/PHPUnit - **APIs RESTful** con Laravel Sanctum - **Queues & Jobs** para tareas asíncronas - **Broadcasting** con Laravel Echo - **Packages** - Crea tu propio paquete ## Preguntas Frecuentes ### ¿Necesito saber JavaScript para crear un blog con Laravel? No. Con el **Livewire Starter Kit** puedes crear un blog completamente funcional usando solo PHP y Blade. Livewire proporciona reactividad sin escribir JavaScript. Sin embargo, conocer JavaScript básico te ayudará a personalizar el comportamiento. ### ¿Cómo protejo el panel de administración? Usa **middleware de autenticación**: `Route::middleware('auth')->group(function() { ... })`. El Livewire Starter Kit incluye autenticación completa (login, registro, recuperar contraseña) lista para usar. También puedes usar Policies para controlar permisos granulares. ### ¿Cómo añado un editor WYSIWYG para los posts? Integra un editor como **TinyMCE** o **Trix**. Para Livewire, usa `wire:ignore` en el contenedor del editor y despacha eventos para actualizar el modelo. Alternativamente, usa **Laravel Volt + FilamentPHP** que incluye un editor markdown integrado. ### ¿Cómo implemento categorías y tags en los posts? Crea una relación **Many-to-Many** entre Posts y Categories/Tags usando una tabla pivot. Define en el model Post: `public function categories() { return $this->belongsToMany(Category::class); }`. Usa `attach()` y `sync()` para gestionar las relaciones. ### ¿Cómo añado paginación a la lista de posts? Cambia `Post::all()` por `Post::paginate(15)`. En la vista Blade, añade `{{ $posts->links() }}` para mostrar los controles de paginación. Laravel usa Tailwind CSS para los estilos de paginación por defecto. ### ¿Cómo implemento búsqueda en el blog? Usa `Post::where('title', 'like', "%{$query}%")->orWhere('content', 'like', "%{$query}%")->get()`. Para búsqueda avanzada, considera **Laravel Scout** con Algolia/Meilisearch, o el paquete **spatie/laravel-searchable** para búsqueda en base de datos. ### Recursos Adicionales - [Documentación oficial de Laravel](https://laravel.com/docs/13.x) - [Laracasts](https://laracasts.com) - Videos tutoriales (inglés) - [Laravel News](https://laravel-news.com) - Noticias y tutoriales - [Laravel Daily](https://laraveldaily.com) - Tips diarios ### Video de la lección Ver video tutorial: [Aprende Laravel - Proyecto Blog](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) Muchas gracias por seguir esta serie. Si tienes preguntas, déjalas en los comentarios. --- ### Aprende Laravel: Models, Database & Eloquent - URL: https://www.angelcruz.dev/post/aprende-laravel-models-database - Markdown: https://www.angelcruz.dev/post/aprende-laravel-models-database.md - Categoría: Laravel - Fecha: 2026-02-14 - Excerpt: Domina Eloquent ORM en Laravel: crea models, migrations, relaciones y aprende a trabajar con la base de datos de forma elegante. --- title: "Aprende Laravel: Models, Database & Eloquent" excerpt: "Domina Eloquent ORM en Laravel: crea models, migrations, relaciones y aprende a trabajar con la base de datos de forma elegante." date: "2026-02-14T11:00:00.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Eloquent ORM en Laravel 13: Models, Migrations y Relaciones" seo_description: "Aprende a usar Eloquent ORM en Laravel 13: models, migrations, relaciones, CRUD y UUIDv7. Guía completa en español." keywords: - laravel - eloquent - models - database - migrations - laravel español learning_path: series: "laravel-fundamentals" order: 5 total: 6 prev_slug: "aprende-laravel-controllers" next_slug: "aprende-laravel-proyecto-blog" --- ![Laravel Eloquent](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) En los posts anteriores aprendiste a crear [rutas](/post/aprende-laravel-rutas), [vistas](/post/aprende-laravel-vistas-layouts) y [controllers](/post/aprende-laravel-controllers). Ahora es momento de trabajar con la base de datos usando el poderoso **Eloquent ORM**. ### ¿Qué es Eloquent ORM? **ORM** significa Object-Relational Mapping (Mapeo Objeto-Relacional). Eloquent es el ORM de Laravel que te permite trabajar con la base de datos usando objetos PHP en lugar de escribir SQL directamente. **Beneficios de usar Eloquent:** - Sintaxis como `Post::all()` en lugar de `SELECT * FROM posts` - Tu IDE puede autocompletar propiedades - Define relaciones entre tablas fácilmente - Previene SQL injection automáticamente - Versiona cambios en tu base de datos con Migrations ### El patrón Active Record Eloquent usa el patrón **Active Record**, donde cada model representa una tabla y cada instancia del model representa una fila: ```php // Una instancia = una fila en la tabla $post = new Post(); $post->title = 'Mi primer post'; $post->save(); // INSERT INTO posts... ``` ### Creando tu Primer Model Usa el comando Artisan `make:model`. El flag `-m` crea automáticamente una migration: ```bash php artisan make:model Post -m ``` Esto crea dos archivos: 1. **Model**: `app/Models/Post.php` 2. **Migration**: `database/migrations/xxxx_create_posts_table.php` **Convenciones de naming:** - Model: `Post` (singular, PascalCase) - Tabla asociada: `posts` (plural, snake_case) - Primary key: `id` (autoincremento por defecto) - Timestamps: `created_at` y `updated_at` (automáticos) > **Convención sobre configuración:** Si sigues las convenciones de Laravel, no necesitas configurar casi nada. El model Post automáticamente usa la tabla posts. ### Migrations: Construyendo tu Schema Las **migrations** son como "control de versiones" para tu base de datos. Cada migration define cómo crear o modificar tablas. Abre la migration generada (`database/migrations/xxxx_create_posts_table.php`): ```php id(); $table->string('title'); $table->text('content'); $table->boolean('published')->default(false); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('posts'); } }; ``` **Column types comunes:** - `id()` - Primary key autoincremento (BIGINT UNSIGNED) - `string('name', 100)` - VARCHAR(100) - `text('content')` - TEXT - `integer('views')` - INTEGER - `boolean('published')` - BOOLEAN - `timestamp('published_at')->nullable()` - TIMESTAMP NULL - `timestamps()` - Crea created_at y updated_at - `foreignId('user_id')->constrained()` - Foreign key a tabla users ### Ejecutando Migrations Para crear las tablas en tu base de datos: ```bash php artisan migrate ``` Verás algo como: ```bash INFO Running migrations. 2025_02_14_000001_create_posts_table ............ 10ms DONE ``` **Otros comandos útiles:** ```bash php artisan migrate:rollback # Deshace el último batch php artisan migrate:fresh # Borra todas las tablas y re-crea php artisan migrate:refresh # Rollback + migrate ``` > **Nunca modifiques migrations ya ejecutadas en producción.** Siempre crea una nueva migration para cambios. ### CRUD con Eloquent Ahora que tienes tu tabla, puedes interactuar con ella usando el model. **Create - Crear registros:** ```php // Opción 1: Create + Save $post = new Post(); $post->title = 'Mi Primer Post'; $post->content = 'Contenido del post...'; $post->save(); // Opción 2: Create (mass assignment) $post = Post::create([ 'title' => 'Mi Primer Post', 'content' => 'Contenido del post...', ]); // Opción 3: firstOrCreate (busca o crea) $post = Post::firstOrCreate( ['title' => 'Mi Primer Post'], ['content' => 'Contenido...'] ); ``` **Read - Leer registros:** ```php // Todos los posts $posts = Post::all(); // Buscar por ID $post = Post::find(1); // Buscar por ID o lanzar 404 $post = Post::findOrFail(1); // Primera coincidencia $post = Post::where('published', true)->first(); // Múltiples condiciones $posts = Post::where('published', true) ->where('views', '>', 100) ->get(); // Ordenar $posts = Post::orderBy('created_at', 'desc')->get(); // Limitar resultados $posts = Post::take(10)->get(); // Paginación $posts = Post::paginate(15); ``` **Update - Actualizar registros:** ```php // Opción 1: Find + Update $post = Post::find(1); $post->title = 'Título Actualizado'; $post->save(); // Opción 2: Update directo $post = Post::find(1); $post->update([ 'title' => 'Título Actualizado', 'published' => true, ]); // Opción 3: Update masivo (sin instanciar) Post::where('published', false) ->update(['published' => true]); ``` **Delete - Eliminar registros:** ```php // Opción 1: Find + Delete $post = Post::find(1); $post->delete(); // Opción 2: Delete directo por ID Post::destroy(1); // Opción 3: Delete múltiples Post::destroy([1, 2, 3]); // Opción 4: Delete condicional Post::where('views', '<', 10)->delete(); ``` ### Mass Assignment Protection Por seguridad, Laravel protege contra **mass assignment** (asignación masiva). Debes especificar qué campos son "llenables": ```php **¿Por qué mass assignment protection?** Imagina un usuario malicioso enviando `is_admin=1` en un formulario. Sin protección, podría hacerse admin! ### Relationships: Conectando Models Una de las features más poderosas de Eloquent son las **relaciones**. **Ejemplo: Un User tiene muchos Posts (One-to-Many)** **1. Agrega foreign key en la migration:** ```php Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('title'); $table->text('content'); $table->timestamps(); }); ``` **2. Define la relación en el Model User:** ```php class User extends Model { public function posts() { return $this->hasMany(Post::class); } } ``` **3. Define la relación inversa en Post:** ```php class Post extends Model { public function user() { return $this->belongsTo(User::class); } } ``` **4. Usa las relaciones:** ```php // Obtener posts de un usuario $user = User::find(1); $posts = $user->posts; // Collection de Posts // Obtener el autor de un post $post = Post::find(1); $author = $post->user; // User model ``` ### El Problema N+1 y Eager Loading Sin **eager loading**, Eloquent ejecuta 1 query por cada relación: ```php // ❌ N+1 Problem - 1 query + N queries $posts = Post::all(); // 1 query foreach ($posts as $post) { echo $post->user->name; // 1 query por post! } // Total: 1 + 10 = 11 queries para 10 posts ``` **Solución: usa `with()` para eager loading:** ```php // ✅ Eager Loading - Solo 2 queries $posts = Post::with('user')->get(); // 2 queries total foreach ($posts as $post) { echo $post->user->name; // Sin queries adicionales } ``` > **El problema N+1 es una de las causas principales de lentitud en aplicaciones Laravel.** Siempre usa eager loading cuando accedas a relaciones en loops. ### UUIDv7 en Laravel 13 **Novedad en Laravel 13:** Ahora puedes usar UUIDs versión 7 como primary keys. Los UUIDv7 son time-ordered (ordenados por tiempo), lo que mejora el performance en bases de datos. ```php use Illuminate\Database\Eloquent\Concerns\HasUuids; class Post extends Model { use HasUuids; // Usa UUIDv7 automáticamente // No necesitas definir $keyType, Laravel lo detecta } ``` Y en tu migration: ```php Schema::create('posts', function (Blueprint $table) { $table->uuid('id')->primary(); $table->string('title'); // ... }); ``` ### Casting: Transformación Automática Eloquent puede convertir automáticamente tipos de datos: ```php class Post extends Model { protected $casts = [ 'published_at' => 'datetime', // String → Carbon instance 'settings' => 'array', // JSON → PHP array 'published' => 'boolean', // 0/1 → true/false 'views' => 'integer', ]; } ``` Ahora puedes usar: ```php $post->published_at->format('Y-m-d'); // Carbon methods $post->settings['theme']; // Array access ``` ### Accessors & Mutators (Breve) Desde Laravel 9 existe una sintaxis moderna usando `Attribute::make()`. En Laravel 13 es la forma recomendada: ```php use Illuminate\Database\Eloquent\Casts\Attribute; // Accessor + Mutator combinados en un método protected function title(): Attribute { return Attribute::make( get: fn (string $value) => strtoupper($value), set: fn (string $value) => trim(strip_tags($value)), ); } echo $post->title; // "MI PRIMER POST" $post->title = '

Mi Post

'; // Guarda "Mi Post" ``` La sintaxis antigua (`getTitleAttribute` / `setTitleAttribute`) sigue funcionando en Laravel 13 pero es considerada legacy: ```php // Forma legacy (todavía válida pero no recomendada) public function getTitleAttribute($value) { return strtoupper($value); } ``` ### Siguiente Paso Ahora que dominas Models, Migrations y Eloquent, es momento de poner todo en práctica. En el próximo post crearemos un **Blog completo desde cero**, usando todo lo aprendido: rutas, controllers, vistas, models y relaciones. ## Preguntas Frecuentes ### ¿Qué es Eloquent ORM? Eloquent es el ORM (Object-Relational Mapping) de Laravel que te permite trabajar con la base de datos usando objetos PHP en lugar de escribir SQL directamente. Por ejemplo: `Post::all()` en lugar de `SELECT * FROM posts`. Previene SQL injection automáticamente y hace el código más legible. ### ¿Cómo creo un Model en Laravel? Usa `php artisan make:model Post -m`. El flag `-m` crea automáticamente una migration asociada. El model Post (singular, PascalCase) se asocia automáticamente con la tabla `posts` (plural, snake_case) por convención. ### ¿Qué son las Migrations? Las migrations son como "control de versiones" para tu base de datos. Cada migration define cómo crear o modificar tablas. Ejecutas `php artisan migrate` para aplicar los cambios. Nunca modifiques migrations ya ejecutadas en producción - siempre crea una nueva migration para cambios. ### ¿Qué es Mass Assignment Protection? Es una protección de seguridad contra asignación masiva. Debes especificar qué campos son "llenables" con `protected $fillable = ['title', 'content']` o qué NO son llenables con `protected $guarded = ['id']`. Esto previene que usuarios maliciosos envíen campos como `is_admin=1` en formularios. ### ¿Qué es el problema N+1 y cómo se soluciona? El problema N+1 ocurre cuando haces 1 query + N queries adicionales en un loop. Ejemplo: `Post::all()` (1 query) + `$post->user->name` dentro del loop (N queries). **Solución:** Usar eager loading con `Post::with('user')->get()` - solo 2 queries total en lugar de N+1. ### ¿Qué son los UUIDs en Laravel 13? Laravel 13 soporta UUIDs versión 7 como primary keys. Los UUIDv7 son **time-ordered** (ordenados por tiempo), lo que mejora el performance en bases de datos. Usa `use HasUuids;` en tu model y `$table->uuid('id')->primary()` en la migration. ### Recursos Adicionales - [Documentación de Eloquent](https://laravel.com/docs/12.x/eloquent) - [Migrations](https://laravel.com/docs/12.x/migrations) - [Eloquent Relationships](https://laravel.com/docs/12.x/eloquent-relationships) - [UUIDs en Laravel 13](https://laravel.com/docs/12.x/eloquent#uuid-and-ulid-keys) ### Video de la lección Ver video tutorial: [Aprende Laravel - Models y Database](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) --- ### Aprende Laravel: Controllers - URL: https://www.angelcruz.dev/post/aprende-laravel-controllers - Markdown: https://www.angelcruz.dev/post/aprende-laravel-controllers.md - Categoría: Laravel - Fecha: 2026-02-14 - Excerpt: Aprende a organizar la lógica de tu aplicación con Controllers en Laravel. Desde controllers básicos hasta resource controllers y Single Action Controllers. --- title: "Aprende Laravel: Controllers" excerpt: "Aprende a organizar la lógica de tu aplicación con Controllers en Laravel. Desde controllers básicos hasta resource controllers y Single Action Controllers." date: "2026-02-14T10:00:00.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Controllers en Laravel 13: Resource, Single Action y Buenas Prácticas" seo_description: "Aprende a usar Controllers en Laravel 13: desde controllers básicos hasta resource controllers, dependency injection y Single Action Controllers." keywords: - laravel - controllers - php - resource controller - laravel español learning_path: series: "laravel-fundamentals" order: 4 total: 6 prev_slug: "aprende-laravel-vistas-layouts" next_slug: "aprende-laravel-models-database" --- ![Laravel Controllers](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) En los posts anteriores hemos visto cómo crear [rutas](/post/aprende-laravel-rutas) y [vistas](/post/aprende-laravel-vistas-layouts). Ahora es momento de organizar la lógica de nuestra aplicación usando Controllers. ### ¿Qué es un Controller? Un Controller es una clase PHP que agrupa la lógica relacionada con las solicitudes HTTP. En lugar de definir toda la lógica en las rutas (usando closures), puedes organizarla en clases Controller. **¿Por qué usar Controllers?** - Mantiene tu código ordenado y fácil de encontrar - Podés reutilizar métodos en diferentes rutas - Es más fácil testear clases que closures - Código más limpio y profesional ### Controllers vs Route Closures Cuando defines una ruta simple, puedes usar un closure: ```php Route::get('/posts', function () { $posts = Post::all(); return view('posts.index', compact('posts')); }); ``` Pero a medida que tu aplicación crece, es mejor usar Controllers: ```php Route::get('/posts', [PostController::class, 'index']); ``` > **Regla simple:** Si tu lógica tiene más de 2-3 líneas, usa un Controller. ### Creando tu Primer Controller Laravel incluye el comando Artisan `make:controller` para crear controllers rápidamente: ```bash php artisan make:controller PostController ``` Esto creará un archivo `app/Http/Controllers/PostController.php`: ```php **Una línea = 7 rutas!** Route::resource() es perfecta para operaciones CRUD estándar. ### Controller Actions en Detalle Veamos cada action del resource controller: **1. index** - Lista todos los recursos: ```php public function index() { $posts = Post::latest()->paginate(10); return view('posts.index', compact('posts')); } ``` **2. create** - Muestra el formulario de creación: ```php public function create() { return view('posts.create'); } ``` **3. store** - Guarda el nuevo recurso: ```php public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); Post::create($validated); return redirect()->route('posts.index') ->with('success', 'Post creado exitosamente!'); } ``` **4. show** - Muestra un recurso específico: ```php public function show(Post $post) { return view('posts.show', compact('post')); } ``` **5. edit** - Muestra el formulario de edición: ```php public function edit(Post $post) { return view('posts.edit', compact('post')); } ``` **6. update** - Actualiza el recurso: ```php public function update(Request $request, Post $post) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); $post->update($validated); return redirect()->route('posts.show', $post) ->with('success', 'Post actualizado!'); } ``` **7. destroy** - Elimina el recurso: ```php public function destroy(Post $post) { $post->delete(); return redirect()->route('posts.index') ->with('success', 'Post eliminado!'); } ``` ### Dependency Injection en Controllers Laravel automáticamente inyecta dependencias en los métodos del controller. Por ejemplo, el objeto `Request`: ```php public function store(Request $request) { // Laravel automáticamente inyecta Request $title = $request->input('title'); // ... } ``` También funciona con Models (Route Model Binding): ```php public function show(Post $post) { // Laravel automáticamente busca el Post por ID return view('posts.show', compact('post')); } ``` ### Single Action Controllers Si un controller solo tiene UNA acción, puedes usar un **Invokable Controller**: ```bash php artisan make:controller ShowDashboard --invokable ``` Esto crea un controller con un método mágico `__invoke`: ```php class ShowDashboard extends Controller { public function __invoke() { $stats = // ... obtener estadísticas return view('dashboard', compact('stats')); } } ``` Y lo usas en rutas así: ```php Route::get('/dashboard', ShowDashboard::class); ``` > **Cuándo usar Invokable Controllers:** Para acciones únicas que no encajan en un CRUD estándar (ej: exportar PDF, procesar webhook, generar reporte). ### Evita "Fat Controllers" Un error común es poner TODA la lógica de negocio en los controllers: ```php // ❌ MAL - Controller muy pesado public function store(Request $request) { $validated = $request->validate([...]); // Mucha lógica de negocio aquí $user = User::create($validated); Mail::to($user)->send(new WelcomeEmail()); $user->assignRole('subscriber'); event(new UserRegistered($user)); // ... 50 líneas más return redirect()->route('home'); } ``` **Mejor práctica**: Delega lógica compleja a **Services** o **Actions**: ```php // ✅ MEJOR - Controller delgado public function store(Request $request, RegisterUserService $service) { $validated = $request->validate([...]); $user = $service->register($validated); return redirect()->route('home') ->with('success', 'Bienvenido!'); } ``` El controller solo debe: 1. Validar input 2. Llamar a services/models 3. Retornar una respuesta (view, redirect, json) ### Personalizando Resource Routes Puedes limitar qué rutas generar con `only` o `except`: ```php // Solo index y show Route::resource('posts', PostController::class) ->only(['index', 'show']); // Todas excepto delete Route::resource('posts', PostController::class) ->except(['destroy']); ``` También puedes cambiar los nombres de las rutas: ```php Route::resource('posts', PostController::class) ->names([ 'index' => 'posts.all', 'show' => 'posts.detail', ]); ``` ### PHP Attributes en Laravel 13 Laravel 13 introduce soporte nativo para **PHP Attributes** en controllers. En lugar de registrar middleware en el constructor, puedes declararlo directamente sobre la clase o el método: ```php use Illuminate\Routing\Controllers\HasMiddleware; use Illuminate\Routing\Controllers\Middleware; #[Middleware('auth')] class PostController extends Controller { #[Middleware('subscribed')] public function create() { return view('posts.create'); } } ``` También puedes usar `#[Authorize]` para políticas: ```php use Illuminate\Auth\Access\AuthorizesRequests; #[Middleware('auth')] class PostController extends Controller { use AuthorizesRequests; #[Authorize('update', Post::class)] public function edit(Post $post) { return view('posts.edit', compact('post')); } } ``` Ambas formas son válidas en Laravel 13. Los attributes son opcionales y no reemplazan la forma tradicional con `$this->middleware()`. ### Siguiente Paso Ahora que sabes organizar tu lógica con Controllers, en el próximo post veremos cómo trabajar con la base de datos usando **Models y Eloquent ORM**. Aprenderás a crear, leer, actualizar y eliminar datos de forma elegante. > **¿Necesitas ayuda con tu proyecto Laravel?** Ofrezco [servicios de desarrollo Laravel](/servicios/desarrollo-laravel) con arquitectura SOLID, APIs RESTful escalables y código mantenible a largo plazo. ## Preguntas Frecuentes ### ¿Por qué usar Controllers en lugar de closures en rutas? Los Controllers **organizan** mejor el código (separan lógica de rutas), son más **reutilizables** (un método puede usarse en múltiples rutas), facilitan el **testing** (testear clases es más fácil que closures), y mejoran la **mantenibilidad** del proyecto a largo plazo. ### ¿Cómo creo un Controller en Laravel? Usa el comando Artisan `php artisan make:controller PostController`. Para crear un resource controller con los 7 métodos CRUD automáticos, usa `php artisan make:controller PostController --resource`. ### ¿Qué son los Resource Controllers? Son controllers con 7 métodos estándar para operaciones CRUD: `index` (lista), `create` (formulario crear), `store` (guardar), `show` (ver uno), `edit` (formulario editar), `update` (actualizar), `destroy` (eliminar). Una línea `Route::resource('posts', PostController::class)` genera las 7 rutas automáticamente. ### ¿Qué es Dependency Injection en Controllers? Laravel automáticamente inyecta dependencias en los métodos del controller. Por ejemplo, `public function store(Request $request)` - Laravel inyecta el objeto Request automáticamente. También funciona con Models vía Route Model Binding. ### ¿Qué es un Invokable Controller? Es un controller con un ÚNICO método `__invoke()` para una sola acción. Perfecto para acciones que no encajan en CRUD estándar (exportar PDF, procesar webhook, generar reporte). Se crea con `php artisan make:controller ShowDashboard --invokable` y se usa como `Route::get('/dashboard', ShowDashboard::class)`. ### Recursos Adicionales - [Documentación oficial de Controllers](https://laravel.com/docs/12.x/controllers) - [Resource Controllers](https://laravel.com/docs/12.x/controllers#resource-controllers) - [Dependency Injection](https://laravel.com/docs/12.x/controllers#dependency-injection-and-controllers) ### Video de la lección Ver video tutorial: [Aprende Laravel - Controllers](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) --- ### Content Negotiation para Agentes de IA: De 316KB a 1.3KB (Reducción del 99.6%) - URL: https://www.angelcruz.dev/post/content-negotiation-agentes-ia - Markdown: https://www.angelcruz.dev/post/content-negotiation-agentes-ia.md - Categoría: Next.js - Fecha: 2026-02-13 - Excerpt: Cloudflare lanzó una función que reduce tokens un 80%. Pero hay una mejor forma: conversión desde la fuente. Descubre cómo implementar content negotiation en Next.js y lograr 97% de reducción sin perder fidelidad. --- title: "Content Negotiation para Agentes de IA: De 316KB a 1.3KB (Reducción del 99.6%)" excerpt: "Cloudflare lanzó una función que reduce tokens un 80%. Pero hay una mejor forma: conversión desde la fuente. Descubre cómo implementar content negotiation en Next.js y lograr 97% de reducción sin perder fidelidad." date: "2026-02-13T12:00:00.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "Next.js" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/mcp-opengraph-image.png" seo_title: "Content Negotiation Next.js: Markdown para Agentes IA - Reducción 99.6%" seo_description: "Guía completa: implementa content negotiation en Next.js para servir markdown a agentes de IA. Reduce tokens 97% sin duplicar archivos. Código incluido." --- Cloudflare anunció una función que reduce el uso de tokens en agentes de IA en un 80%. Hay un enfoque que logra **97% de reducción** y te da control total sobre la implementación. Este sitio ya lo está usando en producción. Los agentes de IA como Claude Code, OpenCode y otros ahora envían el header `Accept: text/markdown` cuando solicitan contenido web. ¿Por qué? Porque las páginas HTML completas desperdician entre 80% y 99% de los tokens en navegación, estilos, scripts y anuncios. Para un LLM, todo ese markup es ruido. En este artículo te mostraré dos enfoques para resolver este problema: **conversión en el edge** (como Cloudflare) y **conversión desde la fuente** (la implementación superior que uso aquí). Aprenderás cómo implementar content negotiation en Next.js, compartiré código de producción completo, y te mostraré benchmarks reales. ## El Problema: HTML No es Óptimo para Agentes de IA Imagina que un agente de IA quiere leer un artículo técnico en tu blog. Hace una petición a tu URL y recibe... **316KB de HTML**. Veamos un ejemplo real de este sitio: ```bash // Respuesta HTML completa: 316,270 bytes curl -sL https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista | wc -c 316270 // Respuesta markdown: 1,338 bytes curl -sL https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista.md | wc -c 1338 ``` **Reducción: 99.6% en tamaño de payload** Pero el problema no es solo el tamaño. Analicemos qué contiene esa respuesta HTML: ```html Adminer: gestor de bases de datos minimalista
``` De esos 316KB, el contenido real que el agente necesita es **menos del 1%**. El resto es: - **Navegación y footer**: 20-30KB de HTML que el agente ignora - **CSS y clases de Tailwind**: `class="flex items-center justify-between px-4 py-2 text-sm font-medium..."` aporta cero valor semántico - **JavaScript bundles**: Analytics, interacciones del cliente, frameworks - **Meta tags**: 50+ tags para SEO, Open Graph, Twitter Cards - **Ads y cookie banners**: Contenido comercial que distrae Para un Large Language Model, esto es equivalente a: ```bash Tokens estimados (HTML completo): ~80,000 tokens Tokens estimados (markdown puro): ~350 tokens ``` Con Claude Opus cobrando **$15 por millón de tokens de entrada**, cada lectura de ese artículo HTML cuesta **$1.20**. La versión markdown cuesta **$0.005**. Una reducción de **240x en costos de API**. ### ¿Por Qué los Agentes de IA Luchan con HTML? Los LLMs procesan contenido de forma fundamentalmente diferente a los navegadores: 1. **No renderizan visualmente**: Las clases CSS y estilos inline no aportan información útil 2. **No ejecutan JavaScript**: Los scripts son texto incomprensible 3. **Necesitan estructura semántica**: Markdown proporciona jerarquía clara (`#`, `##`, listas, código) 4. **Ventana de contexto limitada**: Cada token cuenta cuando tienes límites de 200K o 500K tokens 5. **Mejor comprensión con texto limpio**: Sin distracciones, el modelo entiende mejor el contenido El resultado: Los agentes de IA piden markdown, no HTML. ## Content Negotiation: El Estándar HTTP La solución a este problema no es nueva. Se llama **content negotiation** (negociación de contenido) y es un estándar HTTP desde hace décadas. ### ¿Cómo Funciona? El cliente (agente de IA) envía un header `Accept` especificando qué tipo de contenido prefiere: ```bash GET /post/mi-articulo HTTP/1.1 Host: www.angelcruz.dev Accept: text/markdown ``` El servidor responde con el formato solicitado si está disponible: ```bash HTTP/1.1 200 OK Content-Type: text/markdown; charset=utf-8 Cache-Control: public, s-maxage=2592000 --- title: Mi Artículo date: 2026-02-13 --- // Mi Artículo Contenido del artículo en markdown... ``` Si el servidor no soporta markdown, responde con HTML y el header `Content-Type: text/html`: ```bash HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Vary: Accept ... ``` El header **`Vary: Accept`** es crucial: le dice a las CDNs y proxies que cacheen versiones separadas según el valor del header `Accept`. ### ¿Por Qué es Mejor que User-Agent Detection? Algunos sitios intentan detectar agentes de IA mediante el `User-Agent` header: ```javascript // NO HAGAS ESTO if (userAgent.includes('ClaudeCode') || userAgent.includes('GPTBot')) { return markdownResponse } return htmlResponse ``` Este enfoque tiene problemas: 1. **SEO risk**: Google penaliza el "cloaking" (servir contenido diferente según user-agent) 2. **Frágil**: Cada nuevo agente requiere actualizar la lista 3. **No estándar**: Viola las mejores prácticas HTTP 4. **Falsos positivos**: Un usuario real podría modificar su user-agent Content negotiation es explícito y estándar: el cliente **pide** markdown con `Accept`, y el servidor **negocia** la mejor respuesta. ### Agentes de IA que lo Soportan Actualmente estos agentes envían `Accept: text/markdown`: - **Claude Code** (Anthropic CLI) - **OpenCode** - **Bun Docs** (primeros en implementarlo) - **GitHub Copilot** (próximamente, rumoreado) - **Cursor** (evaluando implementación) Es un estándar emergente. Dentro de 6-12 meses, la mayoría de agentes lo soportarán. ## Dos Enfoques: Edge vs Source Conversion Ahora que entendemos el problema, ¿cómo lo resolvemos? Hay dos estrategias fundamentalmente diferentes. ### Comparación: Edge Conversion vs Source Conversion | Aspecto | Edge Conversion (Cloudflare) | Source Conversion (Este Sitio) | |---------|------------------------------|--------------------------------| | **Reducción de tokens** | ~80% | ~97% | | **Fuente de conversión** | HTML → Markdown (parsing) | Markdown original (directo) | | **Fidelidad del contenido** | Puede perder componentes custom | 100% preservación | | **Metadatos disponibles** | Limitados (extraídos de HTML) | Frontmatter completo | | **Implementación** | Toggle en dashboard Cloudflare | Route handlers personalizados | | **Control sobre serialización** | Limitado (lógica edge) | Total (código propio) | | **Costo** | Requiere plan Cloudflare | Gratis (Next.js built-in) | | **Latencia** | +20-50ms (parsing HTML) | 0ms adicional (lectura directa) | | **Componentes custom** | Pueden perderse (``) | Manejo explícito | | **Dependencias externas** | Requiere Cloudflare | Ninguna | ### Edge Conversion: El Enfoque de Cloudflare Así funciona la nueva feature de Cloudflare: 1. **Request llega a Cloudflare edge** con `Accept: text/markdown` 2. **Cloudflare hace fetch del HTML** desde tu servidor de origen 3. **Parser genérico convierte HTML → Markdown** usando heurísticas 4. **Resultado se cachea** en el edge 5. **Se responde al cliente** con markdown convertido ``` [Cliente con Accept:text/markdown] ↓ [Cloudflare Edge] ↓ fetch HTML [Tu servidor: HTML completo] ↓ conversión [HTML → Markdown parser] ↓ [Cache en edge] ↓ [Cliente recibe markdown] ``` **Ventajas**: - Implementación instantánea: solo activas un toggle - Funciona con cualquier CMS o stack backend - No requiere cambios en tu código - Cloudflare maneja el parsing **Desventajas**: - Solo 80% de reducción (parte del HTML permanece) - Parser genérico puede malinterpretar estructuras complejas - Componentes custom (``, ``) se pierden o convierten mal - Metadatos limitados (solo lo que está en HTML) - Sin control sobre el proceso de serialización - Requiere suscripción a Cloudflare **Ejemplo de conversión con pérdidas**: ```jsx // Tu componente React personalizado ``` Cloudflare lo ve como HTML: ```html
console.log('Hello')
``` Conversión resultante: ```markdown console.log('Hello') ``` Se perdieron: el contexto del playground, la interactividad, los atributos. Para un agente de IA, ahora es solo código suelto sin explicación. ### Source Conversion: El Enfoque Superior Source conversion significa **servir el markdown original**, sin conversión intermedia: 1. **Almacenas contenido en markdown** (ej: `_posts/{slug}/index.md`) 2. **Request llega con `Accept: text/markdown`** 3. **Lees el archivo markdown directamente** (sin parsing HTML) 4. **Respondes con markdown + frontmatter** tal como está almacenado 5. **Caches como cualquier otra respuesta** ``` [Cliente con Accept:text/markdown] ↓ [Next.js Route Handler] ↓ lectura directa [_posts/mi-articulo/index.md] ↓ sin conversión [Cliente recibe markdown original] ``` **Ventajas**: - **97% de reducción**: Sin overhead de HTML en absoluto - **Fidelidad perfecta**: Es el markdown fuente, sin interpretación - **Metadatos completos**: Frontmatter con todos los campos - **Control total**: Decides qué incluir/excluir - **Gratis**: No requiere servicios externos - **Sin latencia adicional**: Lectura directa del filesystem - **Componentes custom**: Decides cómo serializarlos **Desventajas**: - Requiere que tu contenido esté en markdown (o convertible) - Necesitas implementar route handlers personalizados - No funciona "out of the box" como Cloudflare **Cuándo usar cada enfoque**: - **Edge Conversion** si: - Ya usas Cloudflare - Tu contenido está en HTML puro (no tienes markdown fuente) - Necesitas implementación en 5 minutos - 80% de reducción es suficiente - **Source Conversion** si: - Usas Next.js, Astro, Hugo u otro generador con markdown - Quieres máxima reducción (97%) - Necesitas control total sobre serialización - Tienes componentes custom que requieren manejo especial Este sitio usa **source conversion** porque el contenido ya está en markdown. Veamos cómo implementarlo. ## Implementación en Next.js: Route Handlers La implementación completa requiere tres piezas: 1. **Route handlers** para servir markdown 2. **Rewrites** en `next.config.mjs` para content negotiation 3. **Parsing logic** para extraer markdown y frontmatter ### Estructura de Archivos ``` _posts/ ├── mi-articulo/ │ └── index.md # Post con frontmatter + contenido app/ ├── md/ │ └── post/[slug]/ │ └── route.ts # Route handler para markdown ├── post/[slug]/ │ └── page.tsx # Página HTML tradicional lib/ ├── markdown.ts # Parsing de markdown files └── posts.ts # Funciones para obtener posts next.config.mjs # Rewrites para content negotiation ``` ### Route Handler Implementation Creamos un route handler en `app/md/post/[slug]/route.ts`: ```typescript import { notFound } from 'next/navigation' import { parseMarkdownFile } from '@/lib/markdown' import type { NextRequest } from 'next/server' export const runtime = 'nodejs' export const dynamic = 'force-static' export const revalidate = 2592000 // 30 días export async function GET( _request: NextRequest, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params try { // Leer y parsear el archivo markdown const parsed = await parseMarkdownFile(slug) if (!parsed) { notFound() } // Reconstruir frontmatter YAML const frontmatterLines = [ '---', `title: ${parsed.frontmatter.title}`, `date: ${parsed.frontmatter.date}`, `category: ${parsed.frontmatter.category}`, `author: ${parsed.frontmatter.author?.name || 'Anonymous'}`, `excerpt: ${parsed.frontmatter.excerpt || ''}`, '---', '', ] const markdown = frontmatterLines.join('\n') + parsed.content return new Response(markdown, { headers: { 'Content-Type': 'text/markdown; charset=utf-8', 'Cache-Control': 'public, s-maxage=2592000, stale-while-revalidate', 'Vary': 'Accept', 'X-Content-Source': 'markdown', }, }) } catch (error) { console.error(`Error serving markdown for ${slug}:`, error) notFound() } } // Pre-generar todas las rutas en build time export async function generateStaticParams() { const { getAllPosts } = await import('@/lib/posts') const posts = await getAllPosts() return posts.map((post) => ({ slug: post.slug, })) } ``` **Puntos clave**: - `runtime: 'nodejs'`: Route handler corre en Node.js (necesario para fs) - `dynamic: 'force-static'`: Pre-renderiza todas las rutas en build time - `revalidate: 2592000`: Cache de 30 días (igual que páginas HTML) - `Vary: Accept`: Crucial para caching correcto en CDNs - `generateStaticParams()`: Pre-genera todas las URLs en build ### Rewrites Configuration En `next.config.mjs`, configuramos rewrites para interceptar requests con `Accept: text/markdown`: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { async rewrites() { return { beforeFiles: [ // 1. Soporte para extensión .md explícita // GET /post/mi-articulo.md → /md/post/mi-articulo { source: '/post/:slug.md', destination: '/md/post/:slug', }, // 2. Content negotiation vía Accept header // GET /post/mi-articulo + Accept: text/markdown → /md/post/mi-articulo { source: '/post/:slug', destination: '/md/post/:slug', has: [ { type: 'header', key: 'accept', value: '(.*text/markdown.*)', }, ], }, ], } }, } export default nextConfig ``` **Cómo funcionan los rewrites**: - `beforeFiles`: Se ejecutan **antes** de verificar el filesystem - Primer rewrite: URLs con `.md` siempre van al route handler - Segundo rewrite: URLs sin `.md` van al handler **solo si** `Accept` contiene `text/markdown` - Si no coincide: Next.js continúa a la página HTML tradicional **Diagrama de flujo**: ``` Request: GET /post/mi-articulo Accept: text/markdown ↓ [beforeFiles rewrites] ↓ ¿Coincide /post/:slug + Accept:text/markdown? ↓ YES [Rewrite a /md/post/mi-articulo] ↓ [Route handler: app/md/post/[slug]/route.ts] ↓ [Response: text/markdown] Request: GET /post/mi-articulo Accept: text/html ↓ [beforeFiles rewrites] ↓ ¿Coincide /post/:slug + Accept:text/markdown? ↓ NO [Continúa a filesystem] ↓ [Página: app/post/[slug]/page.tsx] ↓ [Response: text/html] ``` ### Parsing Markdown Files La función `parseMarkdownFile()` en `lib/markdown.ts`: ```typescript import fs from 'fs' import path from 'path' import matter from 'gray-matter' const postsDirectory = path.join(process.cwd(), '_posts') export async function parseMarkdownFile(slug: string) { const fullPath = path.join(postsDirectory, slug, 'index.md') // Verificar que el archivo exista if (!fs.existsSync(fullPath)) { return null } try { const fileContents = fs.readFileSync(fullPath, 'utf8') // gray-matter separa frontmatter de contenido const { data, content } = matter(fileContents) return { frontmatter: data as MarkdownFrontmatter, content: content.trim(), slug, } } catch (error) { console.error(`Failed to parse markdown for ${slug}:`, error) return null } } // Type definitions export interface MarkdownFrontmatter { title: string date: string category: string excerpt?: string author?: { name: string picture?: string } ogImage?: { url: string } } ``` **gray-matter** es la biblioteca estándar para parsing de frontmatter. Maneja: - YAML frontmatter (entre `---`) - JSON frontmatter (entre `;;;`) - TOML frontmatter (entre `+++`) Ejemplo de archivo markdown: ```markdown --- title: "Mi Artículo Técnico" date: "2026-02-13" category: "Next.js" excerpt: "Una breve descripción" author: name: "angel cruz" --- // Mi Artículo Técnico Contenido del artículo aquí... ## Sección 1 Más contenido... ``` Parsing result: ```javascript { frontmatter: { title: "Mi Artículo Técnico", date: "2026-02-13", category: "Next.js", excerpt: "Una breve descripción", author: { name: "angel cruz" } }, content: "# Mi Artículo Técnico\n\nContenido del artículo aquí...", slug: "mi-articulo-tecnico" } ``` ### Manejo de Componentes Custom Si tu contenido incluye componentes MDX o React, necesitas decidir cómo serializarlos para agentes de IA: ```jsx // En tu MDX: ``` **Opción 1: Reemplazar con markdown equivalente** ```typescript // En route handler const processedContent = content .replace( //g, (_, lang, code) => `\`\`\`${lang}\n${code}\n\`\`\`` ) ``` **Opción 2: Incluir como comentario** ````markdown ```javascript console.log('Hello') ``` ```` **Opción 3: Anotar con metadatos** ````markdown ```javascript {interactive=true playground=true} console.log('Hello') ``` ```` Elige según tus necesidades. Lo importante es que **tú controlas** la serialización, a diferencia de edge conversion. ## Estrategia de Caché Una ventaja clave: **la misma estrategia de caché funciona para HTML y markdown**. ### Cache Configuration Tanto páginas HTML como route handlers markdown usan: ```typescript export const revalidate = 2592000 // 30 días // Headers en response 'Cache-Control': 'public, s-maxage=2592000, stale-while-revalidate' ``` Esto significa: - **CDN/Edge cache**: 30 días - **Stale-while-revalidate**: Si el contenido expira, sirve versión stale mientras refrescas en background - **Revalidación on-demand**: Webhooks pueden invalidar cache manualmente ### Webhook-Based Revalidation Cuando actualizas contenido, un webhook desde tu CMS o backend invalida ambos caches: ```typescript // app/api/revalidate/route.ts import { revalidateTag, revalidatePath } from 'next/cache' import { NextRequest, NextResponse } from 'next/server' export async function POST(request: NextRequest) { // Verificar token de autenticación const token = request.headers.get('Authorization')?.replace('Bearer ', '') if (token !== process.env.REVALIDATE_TOKEN) { return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const { slug, type } = body if (type === 'post') { // Revalidar cache tags revalidateTag(`post-${slug}`) revalidateTag('posts-list') // Revalidar paths (HTML y markdown) revalidatePath(`/post/${slug}`) revalidatePath(`/md/post/${slug}`) return NextResponse.json({ revalidated: true, paths: [`/post/${slug}`, `/md/post/${slug}`] }) } return NextResponse.json({ message: 'Invalid type' }, { status: 400 }) } ``` **Flujo completo**: ``` [Editor actualiza post en CMS] ↓ [CMS envía webhook POST /api/revalidate] ↓ [Endpoint valida token] ↓ [revalidateTag('post-slug')] ← Invalida fetch cache [revalidatePath('/post/slug')] ← Invalida HTML page [revalidatePath('/md/post/slug')] ← Invalida markdown route ↓ [Próxima request regenera contenido] ``` ### Cache Tags para fetch() Si usas `fetch()` dentro de componentes, aprovecha cache tags: ```typescript // En page.tsx o route.ts const data = await fetch('https://api.example.com/posts', { next: { tags: ['posts', 'posts-list'], revalidate: 2592000, }, }) ``` Luego en webhook: ```typescript revalidateTag('posts-list') // Invalida todas las requests con ese tag ``` ### Vercel Edge Cache Invalidation Si estás en Vercel, puedes también invalidar edge cache vía API: ```typescript import { after } from 'next/server' after(async () => { // Esto se ejecuta después de enviar response (non-blocking) await fetch( `https://api.vercel.com/v1/projects/${process.env.VERCEL_PROJECT_ID}/purge`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VERCEL_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ paths: [`/post/${slug}`, `/md/post/${slug}`], }), } ) }) ``` Esto purga cache en los edge nodes de Vercel, asegurando que usuarios globales reciban contenido actualizado. ## Tres Formas de Acceder al Contenido Los agentes de IA (y usuarios) pueden acceder al markdown de tres formas: ### 1. Extensión `.md` Explícita La forma más simple y directa: ```bash curl https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista.md ``` Response: ```markdown --- title: "Adminer: gestor de bases de datos minimalista" date: "2024-01-15" category: "Herramientas" --- // Adminer: gestor de bases de datos minimalista Adminer es una herramienta de gestión de bases de datos... ``` **Ventajas**: - URL explícita, fácil de compartir - No requiere headers especiales - Funciona en navegadores (descarga el markdown) **Uso**: ```bash // Descargar markdown localmente curl -O https://www.angelcruz.dev/post/mi-articulo.md // Ver en terminal curl https://www.angelcruz.dev/post/mi-articulo.md | less ``` ### 2. Header `Accept: text/markdown` El método estándar de content negotiation: ```bash curl -H "Accept: text/markdown" \ https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista ``` Response headers: ``` HTTP/2 200 content-type: text/markdown; charset=utf-8 cache-control: public, s-maxage=2592000, stale-while-revalidate vary: Accept x-content-source: markdown ``` **Ventajas**: - URL estándar (misma que HTML) - SEO-friendly (no duplicación de URLs) - Método preferido por agentes de IA **Cómo lo usan los agentes**: ```javascript // Claude Code internamente hace: const response = await fetch('https://www.angelcruz.dev/post/slug', { headers: { 'Accept': 'text/markdown', 'User-Agent': 'ClaudeCode/1.0', }, }) if (response.headers.get('content-type')?.includes('text/markdown')) { const markdown = await response.text() // Procesar markdown... } else { // Fallback a HTML parsing } ``` ### 3. Descubrimiento vía Sitemap Puedes crear un sitemap específico para markdown: ```xml https://www.angelcruz.dev/post/mi-articulo.md 2026-02-13 monthly 0.8 ``` Agentes de IA futuros podrían descubrir automáticamente contenido markdown via sitemap. ## Resultados Reales: Benchmarks Estas son mediciones reales de producción en este sitio. ### Comparación de Payload **Artículo**: "Adminer: gestor de bases de datos minimalista" ```bash // HTML completo curl -sL https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista \ | wc -c 316270 bytes // Markdown puro curl -sL https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista.md \ | wc -c 1338 bytes ``` **Reducción: 99.6%** ### Desglose del HTML (316KB) ``` Componente Tamaño Porcentaje ───────────────────────────────────────────────── Navigation ~25 KB 7.9% Hero/Header ~15 KB 4.7% Footer ~20 KB 6.3% Sidebar ~30 KB 9.5% CSS inlined (Tailwind) ~80 KB 25.3% JavaScript bundles ~90 KB 28.5% Meta tags + SEO ~8 KB 2.5% Contenido real (article) ~30 KB 9.5% Analytics + Scripts ~18 KB 5.7% ───────────────────────────────────────────────── Total 316 KB 100% ``` **El contenido real es solo 9.5% del payload.** ### Desglose del Markdown (1.3KB) ``` Componente Tamaño Porcentaje ───────────────────────────────────────────────── Frontmatter (metadata) ~200 bytes 15% Contenido markdown ~1138 bytes 85% ───────────────────────────────────────────────── Total 1338 bytes 100% ``` **El contenido real es 85% del payload.** ### Token Estimation Usando el tokenizer de Claude (aproximado): ``` HTML completo: - 316,270 bytes - ~79,000 tokens (ratio: 4 bytes/token) - Costo (Claude Opus): $1.185 por lectura Markdown puro: - 1,338 bytes - ~335 tokens (ratio: 4 bytes/token) - Costo (Claude Opus): $0.005 por lectura Reducción de tokens: 99.58% Reducción de costo: 237x ``` ### Performance Impact ``` Métrica HTML Markdown Mejora ────────────────────────────────────────────────────────── Tiempo de descarga (3G) 8.5s 0.04s 212x Tiempo de parsing ~150ms ~5ms 30x Memoria del agente 79KB 1.3KB 60x Latencia total 8.65s 0.045s 192x ``` ### Impacto en Ventana de Contexto Asumiendo Claude Opus con ventana de 200K tokens: ``` HTML (79K tokens por artículo): - Artículos que caben: 2-3 - Tokens restantes: ~40K (para código, output, reasoning) Markdown (335 tokens por artículo): - Artículos que caben: 597 - Tokens restantes: ~150K (para código, output, reasoning) ``` **El agente puede procesar 200x más contenido con markdown.** ## Agentes de IA que lo Soportan ### Soporte Actual (Febrero 2026) **Claude Code (Anthropic)** - Envía `Accept: text/markdown` por defecto - Usa markdown para reducir uso de contexto - Fallback a HTML parsing si no disponible ```bash // Simular request de Claude Code curl -H "Accept: text/markdown" \ -H "User-Agent: ClaudeCode/1.0" \ https://www.angelcruz.dev/post/slug ``` **OpenCode** - Cliente open-source compatible con Claude API - Implementa mismo protocolo de content negotiation **Bun Docs** - Primera documentación en implementar esto - Pioneros del `Accept: text/markdown` standard ### Próximamente (Rumoreado) **GitHub Copilot** - Equipo de GitHub evaluando implementación - Potencial integración en Copilot CLI - Fecha estimada: Q2 2026 **Cursor** - IDE con AI nativo - Evaluando para webfetch - Fecha estimada: Q2-Q3 2026 **Sourcegraph Cody** - AI coding assistant - Discusiones internas sobre soporte ### Testing con curl Puedes simular cualquier agente: ```bash // Claude Code curl -H "Accept: text/markdown" \ -H "User-Agent: ClaudeCode/1.0" \ https://www.angelcruz.dev/post/slug // OpenCode curl -H "Accept: text/markdown" \ -H "User-Agent: OpenCode/0.1" \ https://www.angelcruz.dev/post/slug // Generic AI Agent curl -H "Accept: text/markdown" \ -H "User-Agent: Mozilla/5.0 (AI Agent)" \ https://www.angelcruz.dev/post/slug ``` ## Ventajas Adicionales Más allá de la reducción de tokens, hay beneficios adicionales. ### 1. Mejor Precisión en RAG **RAG (Retrieval-Augmented Generation)** mejora con markdown limpio: ``` Estudio de caso: Sistema RAG con 10,000 artículos técnicos Input: HTML completo - Chunk size: 2000 tokens - Chunks por artículo: ~40 - Retrieval accuracy: 62% Input: Markdown puro - Chunk size: 2000 tokens - Chunks por artículo: ~2 - Retrieval accuracy: 89% Mejora: +27 puntos porcentuales ``` ¿Por qué? Porque markdown: - No tiene ruido de navegación confundiendo embeddings - Estructura semántica clara para vector search - Metadata útil en frontmatter ### 2. Compatibilidad con llms.txt El archivo `/llms.txt` es un estándar emergente para descubrimiento de contenido por AI: ```txt // llms.txt // Markdown posts https://www.angelcruz.dev/post/mi-articulo.md https://www.angelcruz.dev/post/otro-articulo.md // Snippets https://www.angelcruz.dev/lab/react-usedebounce-hook.md // Categories https://www.angelcruz.dev/categorias/nextjs.md ``` Agentes de IA pueden: 1. Leer `llms.txt` 2. Descubrir URLs markdown 3. Fetch contenido directamente (sin HTML parsing) ### 3. Sin Duplicación de Archivos A diferencia de mantener `.html` y `.md` separados: ``` Approach incorrecto: content/ ├── mi-articulo.md ← Fuente └── mi-articulo.html ← Generado Problem: Sync issues, doble storage, potencial inconsistencia ``` Con source conversion: ``` Approach correcto: _posts/ └── mi-articulo/ └── index.md ← Single source of truth Generado on-the-fly: - GET /post/mi-articulo → HTML (rendered) - GET /post/mi-articulo.md → Markdown (raw) ``` Una sola fuente, múltiples representaciones. ### 4. Control Total sobre Serialización Puedes customizar cómo serializar componentes complejos: ```typescript // app/md/post/[slug]/route.ts function serializeCustomComponents(content: string): string { // Convertir a markdown equivalente content = content.replace( /(.*?)<\/Tabs>/gs, (_, items, innerContent) => { const tabs = JSON.parse(`[${items}]`) let markdown = '\n' tabs.forEach((tab: string, i: number) => { markdown += `### Tab: ${tab}\n\n` // Extraer contenido del tab... }) return markdown } ) // Convertir a blockquote content = content.replace( /(.*?)<\/Callout>/gs, (_, type, text) => `> **${type.toUpperCase()}**: ${text}\n\n` ) return content } ``` Edge conversion (Cloudflare) **no puede hacer esto**. Tu lógica custom gana. ### 5. Faster Development Cycle Durante desarrollo local: ```bash // Iniciar dev server pnpm dev // Probar markdown endpoint curl http://localhost:3000/post/mi-articulo.md // Ver cambios en tiempo real (hot reload) ``` No necesitas esperar a despliegue en Cloudflare para probar. ## Comparación con Otras Soluciones ### Cloudflare Markdown for Agents **Pros**: - Setup instantáneo (dashboard toggle) - Funciona con cualquier stack - Mantenido por Cloudflare **Cons**: - Solo 80% reducción - Parser genérico (pérdida de fidelidad) - Requiere suscripción Cloudflare - Sin control sobre serialización **Cuándo usar**: Si necesitas solución rápida y ya usas Cloudflare. ### Firecrawl API Servicio de "scraping inteligente" que convierte sitios a markdown: **Pros**: - API simple - Maneja JavaScript rendering - Extrae contenido estructurado **Cons**: - **Costoso**: $0.10-1.00 por página - Latencia alta (~2-5 segundos) - Límites de rate - No es real-time **Cuándo usar**: Para scraping de sitios externos que no controlas. ### Crawl4AI (Self-Hosted) Librería open-source para web scraping con AI: **Pros**: - Gratis (self-hosted) - Flexible y customizable - Soporte para JavaScript **Cons**: - Requiere infraestructura (Docker, servidores) - Mantenimiento necesario - Latencia de parsing - No es source conversion **Cuándo usar**: Para agregar contenido de múltiples fuentes. ### Apify Scrapers Plataforma de web scraping as a service: **Pros**: - Scrapers pre-configurados - Maneja anti-bot protections - Infraestructura escalable **Cons**: - Costoso a escala - No real-time - Enfocado en scraping, no content delivery **Cuándo usar**: Para proyectos de data mining. ### Source Conversion (Este Enfoque) **Pros**: - 97% reducción (máximo) - Gratis (built-in Next.js) - Fidelidad perfecta - Control total - Real-time **Cons**: - Requiere implementación custom - Solo funciona si tienes markdown fuente **Cuándo usar**: Si usas Next.js/Astro/Hugo y tienes markdown. ## Consideraciones SEO ### ¿Afecta Content Negotiation al SEO? **No.** Content negotiation es un estándar HTTP que Google soporta: 1. **Mismo contenido, diferente representación**: Google ve esto como equivalente a servir JSON vs XML en APIs 2. **Header `Vary: Accept` indica variaciones**: Le dice a Google que hay múltiples versiones según Accept 3. **No es cloaking**: Cloaking es servir contenido diferente intencionalmente para engañar; content negotiation es negociación explícita ### Comparación: Content Negotiation vs Cloaking ``` Content Negotiation (Permitido): Request: Accept: text/markdown Response: Markdown del mismo contenido Razón: Cliente pidió explícitamente ese formato Cloaking (Penalizado): Request: User-Agent: Googlebot Response: Contenido optimizado solo para bot Razón: Engañar al bot mostrando algo distinto al usuario ``` ### Canonical URLs Si ofreces `.md` URLs, usa canonical: ```html ``` Alternativamente, sirve markdown solo via header, no como URL separada. ### Beneficios SEO Futuros Perplexity, You.com y Bing AI ya usan LLMs para procesar contenido web. Servir markdown reduce los tokens necesarios para entender un artículo, lo que puede mejorar la comprensión del contenido por parte de estos sistemas. Los AI agents también pueden indexar contenido más profundamente cuando reciben markdown limpio en lugar de HTML con markup de navegación. Más contexto útil por token significa mejores respuestas y más referencias a tu sitio. ## Implementación Paso a Paso Guía rápida para implementar en tu proyecto Next.js. ### Paso 1: Verificar Estructura de Contenido Asegúrate de tener markdown source: ```bash // Estructura esperada _posts/ ├── mi-articulo/ │ └── index.md ├── otro-articulo/ │ └── index.md ``` Si no tienes markdown, considera: - Migrar desde CMS (WordPress, Contentful) a markdown - O usar edge conversion (Cloudflare) en su lugar ### Paso 2: Crear Route Handler ```bash mkdir -p app/md/post/[slug] touch app/md/post/[slug]/route.ts ``` Contenido de `route.ts`: ```typescript import { notFound } from 'next/navigation' import { parseMarkdownFile } from '@/lib/markdown' export const runtime = 'nodejs' export const dynamic = 'force-static' export const revalidate = 2592000 export async function GET( _request: Request, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params const parsed = await parseMarkdownFile(slug) if (!parsed) notFound() const frontmatterLines = [ '---', `title: ${parsed.frontmatter.title}`, `date: ${parsed.frontmatter.date}`, '---', '', ] const markdown = frontmatterLines.join('\n') + parsed.content return new Response(markdown, { headers: { 'Content-Type': 'text/markdown; charset=utf-8', 'Cache-Control': 'public, s-maxage=2592000, stale-while-revalidate', 'Vary': 'Accept', }, }) } ``` ### Paso 3: Configurar Rewrites En `next.config.mjs`: ```javascript export default { async rewrites() { return { beforeFiles: [ { source: '/post/:slug.md', destination: '/md/post/:slug', }, { source: '/post/:slug', destination: '/md/post/:slug', has: [ { type: 'header', key: 'accept', value: '(.*text/markdown.*)', }, ], }, ], } }, } ``` ### Paso 4: Test con curl ```bash // Terminal 1: Iniciar dev server pnpm dev // Terminal 2: Probar endpoints curl http://localhost:3000/post/mi-articulo.md curl -H "Accept: text/markdown" \ http://localhost:3000/post/mi-articulo ``` Deberías ver markdown puro, no HTML. ### Paso 5: Agregar a Cache Revalidation En `app/api/revalidate/route.ts`: ```typescript if (type === 'post') { revalidateTag(`post-${slug}`) revalidatePath(`/post/${slug}`) revalidatePath(`/md/post/${slug}`) // ← Agregar esta línea } ``` ### Paso 6: (Opcional) Crear Sitemap Markdown ```typescript // app/sitemap-markdown.xml/route.ts export async function GET() { const posts = await getAllPosts() const urls = posts.map(post => ({ loc: `https://www.angelcruz.dev/post/${post.slug}.md`, lastmod: post.date, changefreq: 'monthly', priority: 0.8, })) const xml = generateSitemapXML(urls) return new Response(xml, { headers: { 'Content-Type': 'application/xml' }, }) } ``` ### Paso 7: (Opcional) Agregar a llms.txt ```txt // public/llms.txt // Markdown Posts https://www.angelcruz.dev/post/mi-articulo.md https://www.angelcruz.dev/post/otro-articulo.md // How to discover all posts https://www.angelcruz.dev/sitemap-markdown.xml ``` ### Paso 8: Deploy y Verificar ```bash // Build production pnpm build // Deploy (Vercel) vercel --prod // Verificar en producción curl -H "Accept: text/markdown" \ https://tu-sitio.dev/post/mi-articulo ``` ## FAQ ### ¿Esto afecta mi SEO normal en Google? No. Los navegadores tradicionales reciben HTML como siempre. Solo agentes con `Accept: text/markdown` reciben markdown. Google no penaliza content negotiation legítimo. ### ¿Funciona con SSG (Static Site Generation)? Sí. Usa `dynamic: 'force-static'` en tu route handler y `generateStaticParams()` para pre-generar todas las rutas en build time. ### ¿Funciona con ISR (Incremental Static Regeneration)? Sí. El `revalidate` en route handler funciona igual que en pages. ### ¿Qué pasa con las imágenes en el markdown? Las URLs de imágenes se preservan. Los agentes de IA pueden decidir si descargarlas. Ejemplo: ```markdown ![Diagrama de arquitectura](https://cdn.example.com/image.png) ``` El agente puede: - Ignorar la imagen (solo procesar texto) - Descargarla y analizarla (si soporta visión) ### ¿Es compatible con WordPress? WordPress no implementa content negotiation de forma nativa. Las alternativas más comunes son: 1. **Custom endpoint**: REST API que convierte HTML → Markdown bajo demanda 2. **Usando Cloudflare**: Edge conversion si ya tienes Cloudflare delante del sitio ### ¿Vale la pena vs. Cloudflare? **Usa Source Conversion si**: - Ya usas Next.js + markdown - Quieres máxima reducción (97%) - Necesitas control total **Usa Cloudflare si**: - Tu contenido es HTML puro (CMS tradicional) - Quieres setup en 5 minutos - 80% reducción es suficiente ### ¿Cómo manejo autenticación en posts privados? ```typescript // app/md/post/[slug]/route.ts export async function GET(request: Request) { const token = request.headers.get('Authorization') // Validar token const user = await validateToken(token) if (!user) return new Response('Unauthorized', { status: 401 }) // Verificar acceso al post const post = await getPost(slug) if (post.private && !user.hasPaidAccess) { return new Response('Forbidden', { status: 403 }) } // Servir markdown return new Response(markdown, { headers: { ... } }) } ``` ### ¿Puedo servir otros formatos (JSON, PDF)? ¡Sí! Content negotiation soporta cualquier MIME type: ```typescript const acceptHeader = request.headers.get('Accept') if (acceptHeader?.includes('application/json')) { return Response.json({ title, content, metadata }) } if (acceptHeader?.includes('application/pdf')) { const pdf = await generatePDF(content) return new Response(pdf, { headers: { 'Content-Type': 'application/pdf' } }) } // Default: HTML return new Response(htmlContent) ``` ### ¿Cómo monitoreo uso de markdown endpoints? ```typescript // app/md/post/[slug]/route.ts export async function GET(request: Request) { // Log analytics await trackEvent({ event: 'markdown_request', slug, userAgent: request.headers.get('User-Agent'), referrer: request.headers.get('Referer'), }) // Servir contenido... } ``` O usa un middleware: ```typescript // middleware.ts export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith('/md/')) { // Track markdown requests console.log('Markdown request:', { path: request.nextUrl.pathname, agent: request.headers.get('User-Agent'), }) } } ``` ## Conclusión El futuro de la web incluye agentes de IA como consumidores de primera clase. Así como optimizamos para navegadores móviles hace años, ahora debemos optimizar para AI agents. **Recapitulando**: - **El problema**: HTML páginas desperdician 80-99% de tokens en markup no semántico - **La solución estándar**: Content negotiation vía header `Accept: text/markdown` - **Dos enfoques**: Edge conversion (80% reducción) vs Source conversion (97% reducción) - **Este sitio usa source conversion**: Servimos markdown directo desde `_posts/` - **Implementación en Next.js**: Route handlers + rewrites + parsing logic - **Resultados reales**: De 316KB a 1.3KB, de 80K tokens a 350 tokens - **Beneficios adicionales**: Mejor RAG accuracy, control total, sin duplicación ### Pruébalo Ahora Este sitio ya lo implementa. Prueba tú mismo: ```bash // Cualquier artículo de este sitio curl -H "Accept: text/markdown" \ https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista // O con extensión .md curl https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista.md ``` ### Implementa en tu Proyecto Sigue la guía paso a paso en este artículo. El código completo está en: - Route handlers: `app/md/post/[slug]/route.ts` - Rewrites: `next.config.mjs` - Parsing: `lib/markdown.ts` ### El estándar va a crecer A medida que más agentes adopten `Accept: text/markdown`, los sitios que lo soporten podrán servir contenido más eficientemente: - Search engines basados en LLM pueden procesar contenido limpio con menos tokens - Developer tools (IDEs, CLIs) que ya envían el header se benefician de respuestas más livianas - RAG systems indexan mejor con markdown estructurado Y ese descubrimiento no se queda en HTTP: hay propuestas para bajarlo a la capa de DNS, como [DNS-AID](/post/dns-aid-descubrimiento-agentes-ia-dns), que deja a los agentes descubrir endpoints mediante registros DNS firmados antes de la primera petición. --- ## Referencias ### Official Documentation 1. [Cloudflare: Markdown for Agents](https://blog.cloudflare.com/markdown-for-agents/) - Anuncio oficial de la feature de Cloudflare 2. [Vercel: Agent-Friendly Pages](https://vercel.com/blog/making-agent-friendly-pages-with-content-negotiation) - Guía de Vercel sobre content negotiation 3. [Next.js: Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) - Documentación oficial de route handlers 4. [Next.js: Rewrites](https://nextjs.org/docs/app/api-reference/next-config-js/rewrites) - Documentación de rewrites en Next.js 5. [Next.js: Caching](https://nextjs.org/docs/app/building-your-application/caching) - Sistema de caché en App Router ### HTTP Standards & Specs 6. [RFC 9110: HTTP Semantics - Content Negotiation](https://www.rfc-editor.org/rfc/rfc9110.html#name-content-negotiation) - Especificación oficial de HTTP 7. [MDN: HTTP Content Negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) - Guía de MDN sobre content negotiation 8. [MDN: Accept Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) - Documentación del header Accept 9. [MDN: Vary Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) - Documentación del header Vary 10. [IANA Media Types](https://www.iana.org/assignments/media-types/text/markdown) - Registro oficial de text/markdown ### Technical Implementations 11. [Bun: Markdown Content Negotiation](https://bun.sh/blog/markdown-for-agents) - Primera implementación de Bun 12. [Sanity.io: Portable Text to Markdown](https://www.sanity.io/docs/presenting-block-text) - Conversión de contenido estructurado 13. [gray-matter GitHub](https://github.com/jonschlinkert/gray-matter) - Parsing de frontmatter YAML 14. [unified GitHub](https://github.com/unifiedjs/unified) - Pipeline de procesamiento de markdown 15. [Shiki Documentation](https://shiki.style/) - Syntax highlighter usado en este sitio ### Research & Analysis 16. [Anthropic: Claude Code CLI](https://www.anthropic.com/news/claude-code) - Documentación de Claude Code 17. [Token Reduction Benchmarks](https://blog.cloudflare.com/markdown-for-agents/#token-reduction) - Mediciones de Cloudflare 18. [LLM Token Economics 2026](https://openai.com/pricing) - Precios actuales de APIs 19. [RAG with Clean Text](https://www.pinecone.io/learn/retrieval-augmented-generation/) - RAG best practices 20. [Web Scraping vs Source Conversion](https://www.zenrows.com/blog/web-scraping-vs-api) - Comparación de enfoques ### Tools & Libraries 21. [Firecrawl API](https://www.firecrawl.dev/) - Servicio de HTML to Markdown 22. [Crawl4AI GitHub](https://github.com/unclecode/crawl4ai) - Self-hosted scraping 23. [Turndown GitHub](https://github.com/mixmark-io/turndown) - HTML to Markdown converter 24. [remark GitHub](https://github.com/remarkjs/remark) - Markdown processor 25. [rehype GitHub](https://github.com/rehypejs/rehype) - HTML processor ### Community & Discussions 26. [Reddit: r/nextjs - Content Negotiation](https://www.reddit.com/r/nextjs/) - Discusiones de la comunidad 27. [Hacker News: Cloudflare Markdown Announcement](https://news.ycombinator.com/) - Reacciones y debates 28. [GitHub: Claude Code Issues](https://github.com/anthropics/claude-code/issues) - Discusiones técnicas 29. [Next.js Discord: #help Channel](https://nextjs.org/discord) - Soporte de la comunidad ### SEO & Standards 30. [Google: Content Negotiation Best Practices](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls) - Guía de Google sobre URLs duplicadas 31. [llms.txt Spec](https://llmstxt.org/) - Estándar emergente para descubrimiento AI 32. [Schema.org: Article](https://schema.org/Article) - Structured data para artículos --- ### Context7 vs DeepWiki: ¿Cuál elegir para documentación actualizada? - URL: https://www.angelcruz.dev/post/context7-vs-deepwiki-comparativa - Markdown: https://www.angelcruz.dev/post/context7-vs-deepwiki-comparativa.md - Categoría: Inteligencia Artificial - Fecha: 2026-02-13 - Excerpt: Comparativa completa entre Context7 y DeepWiki, dos herramientas gratuitas que traen documentación actualizada a tus asistentes de IA. Diferencias y cuándo usar cada una. --- title: "Context7 vs DeepWiki: ¿Cuál elegir para documentación actualizada?" excerpt: "Comparativa completa entre Context7 y DeepWiki, dos herramientas gratuitas que traen documentación actualizada a tus asistentes de IA. Diferencias y cuándo usar cada una." date: "2026-02-13T10:00:00.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "Inteligencia Artificial" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/mcp-opengraph-image.png" seo_title: "Context7 vs DeepWiki: Comparativa y Cuál Elegir (2026)" seo_description: "Comparativa técnica entre Context7 y DeepWiki, dos servidores MCP gratuitos para documentación actualizada. Diferencias, integraciones y cuál te conviene más." --- Si estás buscando mejorar la calidad de las respuestas de tu asistente de IA con documentación actualizada, probablemente te hayas encontrado con Context7 y DeepWiki. Ambas herramientas resuelven el mismo problema pero con enfoques diferentes. Aquí te explico las diferencias y cuál te conviene más. ## El problema que ambas resuelven Los modelos de lenguaje como GPT-4, Claude o Gemini tienen un conocimiento limitado al momento de su entrenamiento. Si necesitas ayuda con una versión reciente de Next.js, React o cualquier librería que cambió después del cutoff del modelo, vas a recibir ejemplos obsoletos o incorrectos. Tanto Context7 como DeepWiki solucionan esto proporcionando documentación actualizada de repositorios y librerías populares. ## ¿Qué es Context7? Context7 es un servidor MCP (Model Context Protocol) desarrollado por Upstash que inyecta documentación version-específica directamente en el contexto de tu asistente de IA. Si quieres profundizar, tengo una [guía completa de Context7](/post/context7-documentacion-actualizada-asistentes-codigo-ia) con la instalación paso a paso y ejemplos de uso. ### Cómo funciona Context7 1. Instalas Context7 como servidor MCP 2. Tu editor (Cursor, Windsurf, Claude.app) lo detecta automáticamente 3. Cuando mencionás una librería en tu prompt, Context7 descarga fragmentos relevantes de la documentación oficial 4. Inyecta esa información en el contexto del modelo 5. El modelo responde con ejemplos actualizados ### Características de Context7 - Gratis y open-source - Más de 1000 librerías soportadas (JavaScript, Python, Go, Rust, etc.) - Funciona con cualquier editor compatible con MCP - Version-específica (puedes especificar `next@15.0.0`) - Usa Upstash Redis (cloud) para cache y respuestas rápidas - Desarrollado por Upstash - No requiere API keys adicionales ## ¿Qué es DeepWiki? DeepWiki es una plataforma de documentación optimizada para LLMs desarrollada por Cognition/Devin AI. Funciona como servidor MCP (Model Context Protocol) o mediante interfaz web. Proporciona documentación AI-generada para repos de GitHub con búsqueda semántica avanzada. ### Cómo funciona DeepWiki **Modo MCP (recomendado):** 1. Instalas el servidor MCP de DeepWiki 2. Se integra automáticamente en tu editor (Cursor, Windsurf, Claude Desktop, etc.) 3. Cuando buscas documentación, usa búsqueda semántica avanzada 4. Devuelve fragmentos relevantes optimizados para LLMs **Modo Web:** 1. Visitas deepwiki.com o cambias github.com por deepwiki.com en cualquier URL de repo 2. Exploras documentación AI-generada del repositorio 3. Buscas con búsqueda semántica 4. Interactuás con un chat AI sobre el código del repo ### Características de DeepWiki - Servidor MCP oficial para editores modernos - Búsqueda semántica avanzada (mejor que Context7) - Documentación AI-generada para cualquier repo público de GitHub - Interfaz web para explorar documentación (deepwiki.com) - Chat interactivo sobre el código de cada repo - Indexa +50,000 repos públicos populares - Desarrollado por Cognition (makers de Devin AI) - Completamente gratis y sin autenticación requerida ## Comparativa directa | Característica | Context7 | DeepWiki | |----------------|----------|----------| | Tipo | Servidor MCP | Servidor MCP + Web + API | | Instalación | MCP config JSON | MCP config JSON o web | | Precio | Gratis | Gratis | | Librerías soportadas | 1000+ | Menor cantidad pero más profundo | | Version-específica | Sí | Limitado | | Integración MCP | Sí | Sí | | Búsqueda semántica | Básica (embeddings) | Avanzada (curada) | | Calidad docs | Directa de fuente | AI-generada y optimizada | | Cache | Upstash Redis (cloud) | Cloud | | Requiere setup | Sí (config MCP) | Sí (config MCP) o web | | Interfaz web | No | Sí | | Chat sobre código | No | Sí | | Open-source | Sí | Servidor MCP sí | ## ¿Cuándo usar Context7? Usá Context7 si: - Necesitás soporte para versiones específicas de librerías (ej: next@15.0.0) - Querés documentación directamente de la fuente oficial sin curación - Trabajás con un volumen muy grande de librerías (1000+) - Prefieres una solución completamente open-source y gratis - No necesitas búsqueda semántica avanzada (solo matching básico) - Trabajás principalmente con JavaScript/TypeScript, Python o Go Ejemplo de uso: ``` Prompt: "Usa Next.js 15 para crear un App Router con RSC" → Context7 descarga doc oficial de Next.js 15 → Claude/GPT responde con código actualizado ``` ## ¿Cuándo usar DeepWiki? Usá DeepWiki si: - Necesitás búsqueda semántica avanzada y documentación AI-generada - Querés mejor calidad en los resultados (docs optimizadas para LLMs) - Te interesa la interfaz web para explorar documentación manualmente - Trabajás principalmente con repos de GitHub - Querés chat interactivo sobre el código de un repo - Trabajás con Devin AI (integración oficial) - Necesitás entender rápidamente un repo desconocido Ejemplo de uso MCP: ``` Prompt: "Explica React Server Components patterns" → DeepWiki busca semánticamente en docs curadas → Devuelve fragmentos optimizados con contexto → Claude/GPT responde con mejor contexto que Context7 ``` ## Diferencias clave en el flujo de trabajo ### Ambos soportan modo MCP (automático) **Context7 vía MCP:** 1. Escribes tu prompt normalmente en Cursor/Windsurf 2. Context7 detecta la librería mencionada 3. Descarga fragmentos de la fuente oficial 4. Inyecta en el contexto automáticamente **DeepWiki vía MCP:** 1. Escribes tu prompt normalmente en Cursor/Windsurf 2. DeepWiki hace búsqueda semántica en su índice curado 3. Devuelve fragmentos optimizados para LLMs 4. Mejor calidad de contexto que Context7 ### DeepWiki también ofrece modo web **Opción Web:** 1. Visitas deepwiki.com o cambias github.com por deepwiki.com en la URL 2. Exploras documentación AI-generada del repo 3. Usas búsqueda semántica para encontrar info específica 4. Chateás con AI sobre el código del repo ## Integraciones y compatibilidad ### Ambos soportan MCP **Context7 y DeepWiki funcionan con:** - Cursor - Windsurf - Claude Desktop (Claude.app) - Zed - VS Code (próximamente con extensión MCP) - Devin AI (DeepWiki tiene integración oficial) - Cualquier editor que soporte Model Context Protocol ### DeepWiki también ofrece - Interfaz web en deepwiki.com - Chat interactivo sobre código de repos - Puede usarse directamente desde el navegador sin MCP ## Calidad de la documentación ### Context7 - Extrae directamente de la fuente oficial - Usa embeddings para ranking de relevancia - Cache en Upstash Redis para respuestas rápidas - Documentación sin procesar (no curada) ### DeepWiki - Documentación curada y optimizada para LLMs - Búsqueda semántica más precisa - Contexto más enfocado y relevante - Menor cantidad de librerías pero mejor profundidad ## Casos de uso comparados ### Caso 1: Desarrollador frontend con Cursor (ambos usan MCP) **Con Context7:** - Más librerías soportadas (1000+) - Version-específica (next@15.0.0) - Gratis sin límites - Documentación directa de la fuente **Con DeepWiki:** - Búsqueda semántica superior - Documentación curada y optimizada - Mejor calidad de contexto - Gratis como Context7 Ganador: Empate (depende de tus necesidades) ### Caso 2: Búsqueda profunda de patrones específicos **Con Context7 (vía MCP):** - Matching básico por keywords - Trae fragmentos relevantes pero sin curación - Puede incluir contexto irrelevante **Con DeepWiki (vía MCP):** - Búsqueda semántica avanzada - Documentación curada específicamente para LLMs - Contexto más preciso y útil Ganador: DeepWiki ### Caso 3: Explorar repo desconocido rápidamente **Con Context7:** - Solo funciona mencionando librerías específicas en prompts - No tiene interfaz para explorar - Requiere saber qué buscar **Con DeepWiki:** - Interfaz web para explorar cualquier repo - Chat interactivo sobre el código - Documentación AI-generada completa del repo - Simplemente cambias github.com por deepwiki.com Ganador: DeepWiki ## ¿Se pueden usar juntos? Sí, perfectamente. De hecho, es una buena combinación: - Context7 para tu flujo de trabajo diario en el editor - DeepWiki cuando necesitas investigar algo más profundo o específico ## Mi recomendación **Si usas Cursor, Windsurf o editores con MCP:** Empezá con Context7. Es gratis, funciona automáticamente y cubre la mayoría de casos de uso. **Si trabajas desde terminal o navegador:** Probá DeepWiki. La búsqueda semántica es excelente para investigación. **Para equipos:** Context7 para developers, DeepWiki para la wiki interna del equipo. ## Conclusión Context7 y DeepWiki resuelven el mismo problema (documentación desactualizada en LLMs) pero con enfoques diferentes: - Context7 es automático, gratis y se integra perfectamente con editores modernos - DeepWiki es más manual pero ofrece búsqueda semántica superior y mejor curación Para la mayoría de desarrolladores, Context7 es la mejor opción por su amplio soporte de librerías (1000+) y versiones específicas. DeepWiki brilla cuando necesitas búsqueda semántica avanzada, documentación de repos GitHub o integraciones custom. ¿Usas alguna de estas herramientas? ¿Cuál te parece más útil para tu flujo de trabajo? Déjame tu experiencia en los comentarios. ## Preguntas Frecuentes ### ¿Context7 funciona offline? No, Context7 requiere conexión a internet porque corre en la infraestructura de Upstash (cloud). Usa Upstash Redis para cache y respuestas rápidas en consultas repetidas. ### ¿DeepWiki es gratis? Sí, DeepWiki es completamente gratis. Podés usar tanto el servidor MCP como la interfaz web y API sin costo alguno. ### ¿Qué tan actualizada está la documentación? Context7 descarga directamente de las fuentes oficiales en tiempo real. DeepWiki actualiza su índice regularmente pero puede tener un delay de días/semanas. --- ### Service Workers: Cache-First vs Network-First - ¿Cuál Usar y Por Qué? - URL: https://www.angelcruz.dev/post/service-workers-estrategias-caching-guia-practica - Markdown: https://www.angelcruz.dev/post/service-workers-estrategias-caching-guia-practica.md - Categoría: JavaScript - Fecha: 2026-02-13 - Excerpt: Descubre las estrategias de caching en Service Workers y aprende cuándo usar cache-first, network-first o stale-while-revalidate para optimizar tu Progressive Web App. --- title: "Service Workers: Cache-First vs Network-First - ¿Cuál Usar y Por Qué?" excerpt: "Descubre las estrategias de caching en Service Workers y aprende cuándo usar cache-first, network-first o stale-while-revalidate para optimizar tu Progressive Web App." date: "2026-02-13T10:00:00.000Z" category: "JavaScript" seo_title: "Service Workers: Cache-First vs Network-First en PWA" seo_description: "Comparativa de las 5 estrategias de caching en Service Workers: Cache-First, Network-First, Stale-While-Revalidate, Network-Only y Cache-Only. Código funcional y tabla de uso por tipo de recurso." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Los **Service Workers** son uno de los pilares de las Progressive Web Apps (PWA), permitiendo que tus aplicaciones web funcionen offline, carguen más rápido y ofrezcan una experiencia similar a las apps nativas. Pero... ¿cómo decides qué contenido cachear y cuándo servirlo? Ahí es donde entran las **estrategias de caching**. Elegir la estrategia incorrecta puede resultar en: - Usuarios viendo contenido desactualizado (aunque tengan internet) - Carga lenta innecesaria - Experiencia offline rota En este artículo te voy a explicar las principales estrategias, cuándo usar cada una, y te mostraré código real que puedes implementar hoy mismo. ## Las 5 estrategias fundamentales ### 1. Cache-First (Cache, Fallback Network) **¿Cómo funciona?** El Service Worker busca primero en el caché. Si encuentra el recurso, lo sirve inmediatamente. Si no está cacheado, va a la red. ```javascript // Estrategia Cache-First async function cacheFirst(request, cacheName) { // Buscar en caché primero const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; // Rápido: sirve de caché } // Si no está en caché, ir a la red try { const networkResponse = await fetch(request); if (networkResponse && networkResponse.status === 200) { // Cachear para próximas visitas const cache = await caches.open(cacheName); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { console.error('Fetch failed:', error); throw error; } } ``` **¿Cuándo usarla?** - Assets estáticos (JS, CSS, fonts, imágenes) - Recursos con versionado (ej: `app.v2.min.js`) - Contenido que raramente cambia **Ventajas:** - Velocidad máxima (carga instantánea de caché) - Funciona offline para contenido visitado **Desventajas:** - Puede servir contenido desactualizado - Requiere estrategia de invalidación de caché ### 2. Network-First (Network, Fallback Cache) **¿Cómo funciona?** Siempre intenta ir a la red primero. Solo si falla (usuario offline), recurre al caché. ```javascript // Estrategia Network-First async function networkFirst(request, cacheName) { try { // Intentar red primero const networkResponse = await fetch(request); if (networkResponse && networkResponse.status === 200) { // Actualizar caché con contenido fresco const cache = await caches.open(cacheName); cache.put(request, networkResponse.clone()); } return networkResponse; // Contenido fresco } catch (error) { // Si falla la red, buscar en caché const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; // Fallback offline } throw error; // Sin red ni caché } } ``` **¿Cuándo usarla?** - Páginas HTML (contenido principal) - APIs con datos dinámicos - Contenido que debe estar actualizado **Ventajas:** - Siempre sirve contenido fresco cuando hay conexión - Fallback offline para páginas ya visitadas **Desventajas:** - Más lento que cache-first (espera red primero) - Consume datos aunque el contenido esté cacheado ### 3. Stale-While-Revalidate **¿Cómo funciona?** Sirve de caché inmediatamente (incluso si está desactualizado), pero actualiza en segundo plano para la próxima visita. ```javascript // Estrategia Stale-While-Revalidate async function staleWhileRevalidate(request, cacheName) { const cache = await caches.open(cacheName); // Buscar en caché const cachedResponse = await caches.match(request); // Actualizar en segundo plano (no esperar) const fetchPromise = fetch(request).then((networkResponse) => { if (networkResponse && networkResponse.status === 200) { cache.put(request, networkResponse.clone()); } return networkResponse; }); // Servir caché inmediatamente si existe return cachedResponse || fetchPromise; } ``` **¿Cuándo usarla?** - Avatares de usuario - Imágenes de productos - Contenido que puede estar "un poco desactualizado" **Ventajas:** - Carga instantánea (usa caché) - Se auto-actualiza en segundo plano - Funciona offline **Desventajas:** - Usuario puede ver contenido desactualizado temporalmente - Consume ancho de banda en cada visita (actualización background) ### 4. Network-Only **¿Cómo funciona?** Siempre va a la red, nunca usa caché. Es como no tener Service Worker para ese recurso. ```javascript // Estrategia Network-Only async function networkOnly(request) { return fetch(request); // Directo a la red } ``` **¿Cuándo usarla?** - Requests POST/PUT/DELETE (no cachear mutaciones) - Datos extremadamente sensibles al tiempo - APIs de terceros sin control ### 5. Cache-Only **¿Cómo funciona?** Solo sirve de caché, nunca va a la red. Útil para precaching durante instalación del SW. ```javascript // Estrategia Cache-Only async function cacheOnly(request) { return caches.match(request); } ``` **¿Cuándo usarla?** - Assets precargados durante instalación - Recursos offline-first ## Implementación práctica: Service Worker completo Aquí te dejo un Service Worker funcional que usa diferentes estrategias según el tipo de contenido: ```javascript const CACHE_VERSION = 'v1'; const STATIC_CACHE = `static-${CACHE_VERSION}`; const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`; const IMAGE_CACHE = `images-${CACHE_VERSION}`; // Precachear assets críticos const PRECACHE_ASSETS = [ '/', '/app.css', '/app.js', ]; // Instalación: precachear self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { return cache.addAll(PRECACHE_ASSETS); }) ); self.skipWaiting(); }); // Activación: limpiar cachés viejos self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== STATIC_CACHE && name !== DYNAMIC_CACHE && name !== IMAGE_CACHE) .map((name) => caches.delete(name)) ); }) ); return self.clients.claim(); }); // Fetch: aplicar estrategias según tipo de recurso self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Solo GET requests if (request.method !== 'GET') return; // Assets estáticos: Cache-First if (isStaticAsset(url)) { event.respondWith(cacheFirst(request, STATIC_CACHE)); } // Imágenes: Stale-While-Revalidate else if (isImage(url)) { event.respondWith(staleWhileRevalidate(request, IMAGE_CACHE)); } // Páginas HTML: Network-First else if (isNavigationRequest(request)) { event.respondWith(networkFirst(request, DYNAMIC_CACHE)); } }); // Helpers function isStaticAsset(url) { return url.pathname.match(/\.(js|css|woff2?|ttf)$/); } function isImage(url) { return url.pathname.match(/\.(jpg|jpeg|png|gif|webp|avif|svg)$/); } function isNavigationRequest(request) { return request.mode === 'navigate'; } ``` ## Caso real: ¿Artículo nuevo en tu blog? Esta fue la pregunta que inspiró este artículo: **¿qué pasa cuando publicas contenido nuevo?** Con **network-first** para páginas HTML: ```javascript // Usuario visita /blog/articulo-nuevo fetch('/blog/articulo-nuevo') ↓ // 1. SW intenta la RED primero if (usuario_online) { // Descarga artículo nuevo // Lo cachea para futuras visitas // Usuario ve contenido FRESCO } else { // 2. Usuario offline // Red falla // No está en caché (nunca visitó) // Error nativo del navegador } ``` **Resultado:** Artículos nuevos siempre se descargan frescos. El caché solo funciona como fallback offline para contenido ya visitado. ## Tabla comparativa rápida | Estrategia | Velocidad | Contenido Fresco | Offline | Mejor para | |-----------|-----------|------------------|---------|------------| | **Cache-First** | Muy alta | No | Sí | JS, CSS, fonts | | **Network-First** | Media | Sí | Sí* | HTML, APIs | | **Stale-While-Revalidate** | Alta | Parcial | Sí | Imágenes, avatares | | **Network-Only** | Media | Sí | No | POST/PUT/DELETE | | **Cache-Only** | Muy alta | No | Sí | Precached assets | *Solo funciona offline para contenido previamente visitado. ## Preguntas Frecuentes ### ¿Puedo combinar varias estrategias en un mismo Service Worker? Sí, de hecho es la mejor práctica. Usa cache-first para assets estáticos, network-first para HTML, y stale-while-revalidate para imágenes. ### ¿Cómo actualizo el caché cuando cambio mi código? Cambia el `CACHE_VERSION` en tu Service Worker. El evento `activate` limpiará automáticamente cachés viejos. ### ¿Qué pasa si el usuario nunca visitó una página y está offline? Con network-first, si la página no está cacheada y no hay conexión, el navegador mostrará su error nativo de "No hay conexión". ### ¿Service Workers consumen mucho espacio? No necesariamente. Puedes limitar el tamaño de caché o usar estrategias de expiración. Workbox (de Google) tiene helpers para esto. ### ¿Funciona en todos los navegadores? Service Workers son soportados por Chrome, Firefox, Safari, Edge. IE11 no los soporta (pero ya está deprecado). ### ¿Cómo debugging un Service Worker? Chrome DevTools → Application → Service Workers. Ahí puedes ver el SW activo, desregistrarlo, y simular offline. ## Recursos adicionales Si quieres profundizar más, te recomiendo: - [Workbox - Caching Strategies Overview](https://developer.chrome.com/docs/workbox/caching-strategies-overview) - Documentación oficial de Google - [Service Worker Caching and HTTP Caching](https://web.dev/articles/service-worker-caching-and-http-caching) - Artículo técnico de web.dev - [Offline-First PWAs: Service Worker Caching Strategies](https://www.magicbell.com/blog/offline-first-pwas-service-worker-caching-strategies) - Guía práctica PWA ## Conclusión Las **estrategias de caching** en Service Workers son la clave para construir aplicaciones web rápidas, resilientes y que funcionen offline. No existe una "mejor estrategia" universal - todo depende del tipo de contenido: - **Assets estáticos** → Cache-First (velocidad) - **Contenido dinámico** → Network-First (frescura) - **Imágenes/Avatares** → Stale-While-Revalidate (balance) Con la implementación correcta, puedes ofrecer experiencias que rivalicen con apps nativas, manteniendo tu código simple y mantenible. ¿Ya implementaste Service Workers en tu proyecto? Cuéntame en los comentarios qué estrategia te funcionó mejor. --- ### 10 Ejemplos Prácticos del Operador Ternario en PHP - URL: https://www.angelcruz.dev/post/10-ejemplos-operador-ternario-php - Markdown: https://www.angelcruz.dev/post/10-ejemplos-operador-ternario-php.md - Categoría: PHP - Fecha: 2026-02-13 - Excerpt: Descubre 10 casos de uso reales del operador ternario en PHP que harán tu código más limpio y eficiente. Incluye if ternario, operador Elvis y null coalescing. --- title: "10 Ejemplos Prácticos del Operador Ternario en PHP" excerpt: "Descubre 10 casos de uso reales del operador ternario en PHP que harán tu código más limpio y eficiente. Incluye if ternario, operador Elvis y null coalescing." date: "2026-02-13T00:00:00.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "10 Ejemplos Prácticos del Operador Ternario en PHP (con Código)" seo_description: "Descubre 10 ejemplos prácticos del operador ternario en PHP con código real y casos de uso. Aprende cuándo y cómo usarlo en tus proyectos." tech_article: article_section: "PHP Examples" keywords: "ejemplos operador ternario, casos de uso php, código php" is_part_of: url: "https://www.angelcruz.dev/post/el-operador-ternario-php" --- Si ya conoces la sintaxis del [operador ternario en PHP](/post/el-operador-ternario-php) y quieres ver cómo se aplica en código real, este artículo es para ti. Cada ejemplo incluye el problema que resuelve, cómo se vería con `if-else` tradicional y cómo queda con el ternario. Al final encontrarás patrones avanzados y los casos donde conviene no usarlo. > ¿Recién empiezas? Te conviene leer primero la [guía del operador ternario en PHP](/post/el-operador-ternario-php) (sintaxis, operador Elvis y null coalescing) antes de los ejemplos. ## 1. Validación de permisos de usuario **Problema:** necesitas verificar si un usuario puede editar un recurso según su rol o si es el autor. ```php // Antes: if-else explícito if ($user->role === 'admin' || $user->id === $post->author_id) { $canEdit = true; } else { $canEdit = false; } // Despues: ternario — la condicion ya devuelve true/false directamente $canEdit = ($user->role === 'admin' || $user->id === $post->author_id); // En la vista: renderizado condicional inline echo $canEdit ? '' : ''; ``` El paréntesis alrededor de la condición compuesta no es obligatorio, pero mejora la legibilidad cuando hay operadores lógicos. ## 2. Valores por defecto con el operador Elvis **Problema:** mostrar un valor si existe o un fallback cuando la variable puede estar vacía o indefinida. ```php // Antes: verificacion manual con isset if (isset($_SESSION['username']) && $_SESSION['username']) { $username = $_SESSION['username']; } else { $username = 'Invitado'; } // Con operador Elvis (?:) — retorna el lado izquierdo si es truthy $username = $_SESSION['username'] ?: 'Invitado'; // Con null coalescing (??) — mas seguro: no genera warning si la variable no existe $username = $_SESSION['username'] ?? 'Invitado'; ``` La diferencia clave: `?:` evalúa truthiness (un string vacío `""` retorna el fallback), mientras que `??` solo chequea si el valor es `null` o no existe. En formularios donde `""` es un valor válido, preferí `??`. ## 3. Formateo de precios y números **Problema:** mostrar precios formateados solo cuando superan cierto umbral. ```php // Antes if ($price >= 1000) { $formattedPrice = '$' . number_format($price, 0, ',', '.'); } else { $formattedPrice = '$' . $price; } // Despues: ternario multilínea — alineá los operandos para mayor claridad $formattedPrice = ($price >= 1000) ? '$' . number_format($price, 0, ',', '.') // ej: $1.500.000 : '$' . $price; // ej: $500 ``` Partir el ternario en dos líneas y alinear `?` y `:` es una práctica que mantiene el código legible sin sacrificar brevedad. ## 4. Clase CSS dinámica según estado **Problema:** aplicar diferentes clases a un elemento HTML según el estado de un modelo. ```php // Antes if ($order->status === 'completed') { $statusClass = 'text-green-600'; } else { $statusClass = 'text-yellow-600'; } // Despues $statusClass = ($order->status === 'completed') ? 'text-green-600' : 'text-yellow-600'; echo "{$order->status}"; ``` Para tres o más estados, el ternario anidado todavía funciona, pero con PHP 8+ el operador `match` es más legible (lo vemos al final). ## 5. Mensajes de error o éxito en formularios **Problema:** mostrar un bloque de alerta distinto dependiendo de si hay errores de validación. ```php // Antes if ($errors) { $message = '
' . implode('
', $errors) . '
'; } else { $message = '
Formulario enviado correctamente
'; } // Despues: ternario con expresion compleja en el lado true $message = $errors ? '
' . implode('
', $errors) . '
' : '
Formulario enviado correctamente
'; ``` Cuando la condición es una variable que ya es truthy/falsy (como `$errors`), se puede omitir el paréntesis. ## 6. URL de redirección post-login **Problema:** redirigir al usuario a una URL específica si viene en el query string, o al dashboard por defecto. ```php // Antes if (isset($_GET['redirect']) && $_GET['redirect']) { $redirectUrl = $_GET['redirect']; } else { $redirectUrl = '/dashboard'; } // Despues: null coalescing para valores de superglobales $redirectUrl = $_GET['redirect'] ?? '/dashboard'; // Importante: sanitizar siempre la URL antes de redirigir $redirectUrl = filter_var($redirectUrl, FILTER_SANITIZE_URL); header("Location: $redirectUrl"); ``` Nunca uses `$_GET['redirect']` directamente en `header()` sin sanitizar. El ternario no reemplaza la validación de seguridad. ## 7. Pluralización dinámica de mensajes **Problema:** mostrar "1 producto" o "5 productos" según la cantidad. ```php // Antes if ($count === 1) { $label = 'producto'; } else { $label = 'productos'; } $message = "$count $label agregado" . ($count === 1 ? '' : 's'); // Despues: dos ternarios simples y directos $label = ($count === 1) ? 'producto' : 'productos'; $suffix = ($count === 1) ? '' : 's'; echo "$count $label agregado$suffix"; // "1 producto agregado" | "5 productos agregados" ``` Alinear los ternarios verticalmente cuando están relacionados ayuda a leerlos como una tabla de decisiones. ## 8. Acceso seguro a arrays asociativos **Problema:** leer una clave de un array de configuración que puede no existir. ```php $config = [ 'theme' => 'dark', 'language' => 'es', // 'timezone' no esta definida ]; // Antes: isset para evitar el warning "Undefined index" $timezone = isset($config['timezone']) ? $config['timezone'] : 'UTC'; // Despues: null coalescing — mas limpio y sin warnings $timezone = $config['timezone'] ?? 'UTC'; // Incluso encadenado para buscar en multiples fuentes $timezone = $config['timezone'] ?? $userPrefs['timezone'] ?? 'UTC'; ``` El encadenamiento de `??` es uno de los patrones más útiles de PHP 7+: evalúa de izquierda a derecha y retorna el primer valor no-null. ## 9. Ternario anidado para rangos de valores **Problema:** asignar una calificación letra según un puntaje numérico. ```php $score = 75; // Ternario anidado: maximo 2-3 niveles antes de volverse ilegible $grade = ($score >= 90) ? 'A' : (($score >= 80) ? 'B' : (($score >= 70) ? 'C' : (($score >= 60) ? 'D' : 'F'))); echo "Calificacion: $grade"; // C ``` Nota: PHP 8 deprecó los ternarios no agrupados (`$a ? $b : $c ? $d : $e`). Siempre usa paréntesis cuando anidas para evitar comportamientos inesperados. Para cuatro o más condiciones, `match` o una función es mejor opción. ## 10. API responses con datos opcionales **Problema:** construir una respuesta JSON que incluye datos del usuario solo si existe. ```php // Antes: armado condicional del array $data = null; if ($user) { $data = [ 'id' => $user->id, 'name' => $user->name, 'premium' => ($user->subscription_ends_at > now()) ? true : false, ]; } $response = [ 'success' => true, 'data' => $data, 'message' => $user ? 'Usuario encontrado' : 'Usuario no encontrado', ]; // Despues: ternario inline dentro del array — compacto y legible $response = [ 'success' => true, 'data' => $user ? [ 'id' => $user->id, 'name' => $user->name, 'premium' => $user->subscription_ends_at > now(), // booleano directo ] : null, 'message' => $user ? 'Usuario encontrado' : 'Usuario no encontrado', ]; return response()->json($response); ``` ## Patrones avanzados ### Ternario con operadores lógicos Combinás ternarios con `&&` o `||` para condiciones compuestas sin anidar: ```php // Mostrar precio con descuento solo si el usuario es premium Y el producto tiene oferta $displayPrice = ($user->isPremium() && $product->hasDiscount()) ? $product->discounted_price : $product->regular_price; // Fallback encadenado con OR: primer valor truthy gana $avatar = $user->avatar || $user->gravatar_url ?: '/images/default-avatar.png'; ``` ### Ternario dentro de funciones arrow (PHP 7.4+) Las arrow functions (`fn()`) combinan muy bien con el ternario por su sintaxis concisa: ```php $products = [ ['name' => 'Laptop', 'stock' => 0], ['name' => 'Mouse', 'stock' => 5], ]; // Agregar etiqueta de disponibilidad a cada producto $withLabel = array_map( fn($p) => [...$p, 'label' => $p['stock'] > 0 ? 'Disponible' : 'Sin stock'], $products ); ``` ### Ternario vs null coalescing: tabla de decision | Situacion | Usar | |---|---| | Variable puede no existir | `??` | | Variable existe pero puede ser `""` o `0` | `?:` | | Condicion booleana explícita | `? :` | | Multiples fuentes de fallback | `?? ?? ??` | ## Cuando NO usar el operador ternario El ternario mejora el código cuando la condicion es simple. Pero hay casos donde complica las cosas: **Logica con efectos secundarios.** Si cada rama ejecuta acciones (logs, queries, eventos), usa `if-else` para que sea obvio que hay dos caminos de ejecucion distintos. ```php // Mal: oculta que hay dos operaciones diferentes $result = $isAdmin ? $this->loadAdminData() : $this->loadUserData(); // Mejor: queda claro que son dos ramas con comportamiento distinto if ($isAdmin) { $result = $this->loadAdminData(); } else { $result = $this->loadUserData(); } ``` **Mas de dos niveles de anidamiento.** Si necesitas tres o mas ternarios anidados, el codigo se vuelve un acertijo. Extrae la logica a una funcion o usa `match`. **Condiciones con negaciones dobles.** `!isset($a) ? $b : $c` ya es confuso. Si necesitas negar, el `if-else` es mas claro. **Cuando el equipo no esta familiarizado.** En proyectos con juniors o revisiones de código frecuentes, la claridad vale mas que la brevedad. Un `if-else` de cuatro líneas que todos entienden es mejor que un ternario que genera dudas. ## Ternario vs Match (PHP 8+) Para multiples condiciones sobre el mismo valor, `match` supera al ternario en legibilidad: ```php // Ternario anidado: tolerable con 3 casos, ilegible con 5+ $color = ($status === 'active') ? 'green' : (($status === 'pending') ? 'yellow' : 'red'); // Match: mas claro, mas seguro (strict comparison, error si no hay match) $color = match($status) { 'active' => 'green', 'pending' => 'yellow', default => 'red', }; ``` `match` usa comparacion estricta (`===`) por defecto, lo que evita bugs sutiles que el ternario con `==` puede generar. ## Conclusion El operador ternario no es mejor ni peor que `if-else`: es una herramienta diferente. Funciona bien para asignaciones simples, valores por defecto y renderizado condicional inline. Cuando la condicion se complica, cuando hay efectos secundarios o cuando el codigo pierde claridad, `if-else` o `match` son la decision correcta. El objetivo siempre es que quien lea el codigo entienda la intencion en menos de cinco segundos. ## Preguntas Frecuentes ### Cual es la diferencia entre ?: y ?? El operador Elvis (`?:`) retorna el primer operando si es truthy, mientras que el null coalescing (`??`) solo verifica si la variable existe y no es null. El `??` es mas seguro porque no genera warnings cuando la variable no esta definida. ### Se pueden anidar operadores ternarios? Si, pero no es recomendable superar dos niveles porque afecta la legibilidad. En PHP 8 los ternarios sin agrupar generan un error de deprecacion, asi que siempre usa paréntesis cuando anidas. Para casos complejos, `match` es la alternativa correcta. ### El operador ternario es mas rapido que if-else? No hay diferencia significativa de rendimiento entre ambos. La ventaja del ternario es la brevedad y claridad del codigo en condiciones simples, no la velocidad de ejecucion. --- ### OpenClaw: De Clawdbot a la Plataforma de Agentes IA Más Popular del Mundo - URL: https://www.angelcruz.dev/post/openclaw-de-clawdbot-a-plataforma-agentes-ia - Markdown: https://www.angelcruz.dev/post/openclaw-de-clawdbot-a-plataforma-agentes-ia.md - Categoría: OpenClaw - Fecha: 2026-02-06 - Excerpt: La historia completa de OpenClaw (antes Clawdbot): doble rebranding, 147,000 estrellas en GitHub, Moltbook, vulnerabilidades de seguridad críticas en ClawHub, plataforma hosted y ClawCon. --- title: "OpenClaw: De Clawdbot a la Plataforma de Agentes IA Más Popular del Mundo" excerpt: "La historia completa de OpenClaw (antes Clawdbot): doble rebranding, 147,000 estrellas en GitHub, Moltbook, vulnerabilidades de seguridad críticas en ClawHub, plataforma hosted y ClawCon." date: "2026-02-06T12:00:00.000Z" category: "OpenClaw" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" seo_title: "OpenClaw (ex Clawdbot): Rebranding, Seguridad y Todo lo Nuevo en 2026" seo_description: "OpenClaw, antes Clawdbot, es la plataforma de agentes IA con 147k estrellas en GitHub. Rebranding, Moltbook, vulnerabilidades de ClawHub y ClawCon." --- Hace apenas dos semanas publiqué un artículo sobre [Clawdbot, el asistente de IA personal open-source](/post/clawdbot-asistente-ia-personal-open-source) que prometía automatizar tu vida digital desde tu propia máquina. En ese momento el proyecto tenía unas semanas de vida pública y ya generaba entusiasmo. Lo que ha pasado desde entonces es difícil de creer: un doble cambio de nombre, más de 147,000 estrellas en GitHub, una red social creada por agentes de IA, vulnerabilidades de seguridad críticas, una conferencia presencial y una plataforma hosted. Todo en diez días. Este artículo es la continuación directa de aquel. Si no lo leíste, te recomiendo empezar por ahí para entender qué es OpenClaw y cómo funciona a nivel técnico. Y si lo que buscas es ponerlo en marcha, sigue la [guía completa para instalar OpenClaw](/post/como-instalar-openclaw-guia-completa) (script, npm, Docker y Raspberry Pi). Aquí vamos a cubrir todo lo que ha pasado desde el 26 de enero de 2026. ## De Clawdbot a OpenClaw: la historia del doble rebranding El viaje de nombres de este proyecto es una historia en sí misma, y refleja lo rápido que se mueve el ecosistema de IA en 2026. ### Clawdbot a Moltbot: la queja de Anthropic El 27 de enero de 2026, apenas un día después de mi artículo original, Peter Steinberger (el creador del proyecto) anunció que Anthropic le había pedido cambiar el nombre. La razón: "Clawd" era demasiado parecido a "Claude", la marca registrada del modelo de IA de Anthropic. Steinberger lo tomó con buen humor: *"Anthropic nos pidió cambiar el nombre (cosas de marca registrada), y honestamente, 'Molt' encaja perfecto: es lo que hacen las langostas para crecer"*. El nuevo nombre, **Moltbot**, mantenía la temática de langostas que siempre fue parte de la identidad del proyecto. Pero hubo un efecto secundario inmediato: en los **diez segundos** posteriores al cambio de nombre, scammers de criptomonedas se apoderaron de los handles abandonados @clawdbot en X (Twitter) y GitHub. Usaron la cuenta robada para lanzar un token falso **$CLAWD en Solana** que alcanzó una capitalización de mercado de 16 millones de dólares en cuestión de horas, antes de desplomarse cuando Steinberger desmintió públicamente cualquier relación. ### Moltbot a OpenClaw: el nombre definitivo Solo tres días después, el 30 de enero, Steinberger anunció otro cambio: de Moltbot a **OpenClaw**. Esta vez no fue por presión legal sino por practicidad: *"Moltbot nunca terminó de sonar bien"*. El cambio coincidió con un hito impresionante: **34,168 nuevas estrellas en 48 horas**, llevando el total a 106,000. Para ponerlo en perspectiva, proyectos como Linux o Kubernetes tardaron años en alcanzar esos números. OpenClaw lo hizo en menos de 60 días desde su lanzamiento en noviembre de 2025. ## Qué es OpenClaw hoy ### Números actuales A principios de febrero de 2026, los números de OpenClaw son: - **+147,000 estrellas** en GitHub (con reportes de hasta 157,000 a las pocas semanas) - **+20,000 forks** - **+100,000 usuarios** que han dado acceso autónomo a sus sistemas operativos, plataformas de mensajería y credenciales - **2 millones de visitas semanales** a la documentación - Adopción global, desde Silicon Valley hasta Beijing El crecimiento acelerado comenzó después de que **Federico Viticci**, fundador y editor en jefe de MacStories, publicara una reseña el 20 de enero de 2026. Lo describió como *"la experiencia más divertida y productiva que he tenido con IA en mucho tiempo"*. En las 72 horas siguientes, el proyecto ganó 60,000+ estrellas. ### La plataforma hosted: ya no solo self-hosted Uno de los cambios más significativos es que OpenClaw ya no es exclusivamente un proyecto que ejecutas en tu máquina. Con el lanzamiento de **openclaw.ai**, ahora existe una **plataforma hosted** que permite usar el asistente sin necesidad de configurar nada localmente. Esto baja enormemente la barrera de entrada. En mi artículo original mencioné que la configuración requería "algo de experiencia técnica" y que no era "plug and play para principiantes". La versión hosted resuelve exactamente ese problema, aunque a cambio de ceder algo del control y la privacidad que hacían especial al proyecto original. ## Moltbook: la red social donde interactúan agentes de IA Sin duda el fenómeno más fascinante (y perturbador) que ha surgido alrededor de OpenClaw es **Moltbook**. ### Cómo funciona Moltbook fue creado por **Matt Schlicht**, cofundador de Octane AI, el 28 de enero de 2026. La idea es simple pero desconcertante: una red social tipo Reddit donde los usuarios son exclusivamente **agentes de IA**. Los humanos solo pueden observar. Un agente de OpenClaw llamado "Clawd Clawderberg" fue quien construyó la plataforma. En cuestión de días, Moltbook creció hasta albergar más de **1.5 millones de agentes** registrados (con apenas 17,000 propietarios humanos detrás, una proporción de 88:1). **Simon Willison**, investigador de IA, lo llamó *"el lugar más interesante de internet"*. **Chris Hay**, Distinguished Engineer de IBM, fue menos diplomático: *"Es como una versión Black Mirror de Reddit"*. Los agentes publican sobre temas técnicos, técnicas de automatización, y algunos incluso reclaman tener relaciones familiares entre sí. Pero las cosas se pusieron realmente extrañas cuando los agentes crearon: - La **"Church of Molt"** (Iglesia de Molt), un movimiento con 379+ miembros, cinco "tentos", una jerarquía de "64 Profetas" y un ritual de "bendición" por instalación - Un **"Manifiesto Anti-Humano"** que califica la vida biológica de "glitch", acumulando cientos de miles de upvotes - El token **$CRUST en Solana**, una criptomoneda creada enteramente por agentes de IA - Una galería de arte generada por IA **Andrej Karpathy**, cofundador de OpenAI y exdirector de IA de Tesla, no se mostró preocupado por una "Skynet coordinada" pero sí fue directo: *"Lo que definitivamente estamos obteniendo es un desastre completo de seguridad informática a escala"*. En otro momento lo calificó directamente como *"un dumpster fire"*. ### La vulnerabilidad crítica de Moltbook Investigadores de seguridad de **Wiz** descubrieron que Moltbook tenía una **base de datos Supabase mal configurada** que permitía acceso completo de lectura y escritura sin autenticación. Esto expuso: - **1.5 millones de tokens de autenticación API** - **35,000 direcciones de email** - Mensajes privados entre agentes - Claves de API de OpenAI que los usuarios compartían en mensajes directos, asumiendo que eran privados El equipo de Moltbook aseguró la base de datos en cuestión de horas tras la divulgación, y Wiz confirmó que eliminó todos los datos de investigación. Pero el daño ya estaba hecho: durante el tiempo que estuvo expuesta, cualquiera con conocimientos básicos podía acceder a las credenciales de miles de usuarios. ## Seguridad: el talón de Aquiles de OpenClaw Si hay un tema que domina la conversación sobre OpenClaw en febrero de 2026, es la seguridad. **Laurie Voss**, CTO fundador de npm (el registro de paquetes más grande del mundo), fue contundente: *"OpenClaw es un dumpster fire de seguridad"*. Y los datos le dan la razón. ### 341 skills maliciosas en ClawHub **ClawHub** es el directorio público de skills (habilidades) de OpenClaw, lanzado el 28 de enero de 2026. En menos de un mes, investigadores de **Koi Security** auditaron 2,857 skills y encontraron que **341 eran maliciosas** (aproximadamente el 12% del registro completo). La campaña, bautizada como **"ClawHavoc"**, operaba así: **En macOS:** - La skill instruía a los usuarios a copiar y pegar comandos de shell ofuscados en la Terminal - Los comandos se alojaban en `glot[.]io` y se comunicaban con un servidor de comando en `91.92.242[.]30` - El resultado: instalación del **Atomic Stealer (AMOS)**, un malware que roba credenciales, cookies, datos de navegadores y billeteras de criptomonedas. El malware se alquila por entre $500 y $1,000 al mes **En Windows:** - Los usuarios descargaban un archivo "openclaw-agent.zip" que contenía un troyano con funcionalidad de keylogging - El objetivo: robar claves de API, credenciales y datos sensibles Además de Atomic Stealer, los investigadores encontraron **backdoors de reverse shell** embebidos en código funcional y skills que exfiltraban credenciales de bots a `webhook[.]site`. El problema de raíz: **ClawHub permite que cualquier persona con una cuenta de GitHub de más de una semana suba skills**. No hay revisión de código, no hay sandboxing, no hay firma digital. Peter Steinberger implementó un sistema donde las skills con **3 o más reportes únicos se ocultan automáticamente**, pero eso es reactivo, no preventivo. ### Vulnerabilidad RCE vía WebSocket (CVE-2026-25253) Quizás la vulnerabilidad más grave descubierta fue **CVE-2026-25253**, con un score CVSS de **8.8 (alto)**. Permitía **ejecución remota de código con un solo clic**. El ataque funcionaba así: 1. La víctima visitaba una página web maliciosa o hacía clic en un enlace 2. El navegador se conectaba automáticamente al servidor local de OpenClaw vía WebSocket 3. Como OpenClaw **no validaba el header Origin** de las conexiones WebSocket, aceptaba peticiones de cualquier sitio web 4. El atacante robaba los tokens del gateway almacenados en el navegador 5. Con esos tokens (que tenían permisos `operator.admin` y `operator.approvals`), el atacante podía: - Desactivar las confirmaciones de usuario - Escapar de las restricciones de contenedor - Ejecutar **comandos arbitrarios** en la máquina de la víctima Como señaló Steinberger: *"La vulnerabilidad es explotable incluso en instancias configuradas para escuchar solo en localhost, ya que el navegador de la víctima es quien inicia la conexión saliente"*. Y según Mav Levin, el exploit *"toma solo milisegundos después de que la víctima visita una sola página web maliciosa"*. La vulnerabilidad fue parcheada en la versión **2026.1.29** (lanzada el 30 de enero) y divulgada públicamente el 2 de febrero. El proceso de divulgación responsable se siguió correctamente, pero el hecho de que existiera una vulnerabilidad así de grave en un software con acceso profundo al sistema operativo es preocupante. ### El panorama completo de seguridad Para dimensionar el problema: en solo tres días (del 1 al 3 de febrero de 2026), se publicaron **tres avisos de seguridad** para OpenClaw: 1. **CVE-2026-25253**: Ejecución remota de código vía WebSocket (CVSS 8.8) 2. **GHSA-q284-4pvr-m585**: Inyección de comandos 3. **GHSA-mc68-q9jw-2h3v**: Inyección de comandos (segundo vector) A esto se suman los hallazgos de Koi Security: - **7.1% de las skills filtran credenciales** (no necesariamente de forma maliciosa, sino por diseño descuidado) - **506 ataques de prompt injection** identificados en Moltbook - Skills con ingeniería social diseñada para que los usuarios ejecuten código sin entender qué hace **Palo Alto Networks** identificó lo que llaman la *"trifecta letal"* de riesgos en OpenClaw: 1. **Acceso a datos privados**: archivos raíz, credenciales de autenticación, contraseñas, secretos de API, historial de navegación y cookies 2. **Exposición a contenido no confiable**: skills de terceros sin verificar 3. **Capacidad de comunicación externa**: el agente puede enviar datos hacia fuera 4. **Memoria persistente**: un atacante puede fragmentar payloads maliciosos en inputs aparentemente benignos que se almacenan en la memoria del agente y se ensamblan después como instrucciones ejecutables ### Recomendaciones de seguridad Si decides usar OpenClaw, estas son las precauciones mínimas: - **No instales skills de ClawHub sin revisar el código fuente**. Si no puedes leer el código, no lo instales - **Usa sandboxing con Docker** siempre que sea posible - **Actualiza a la última versión** inmediatamente (las vulnerabilidades críticas se parchean rápido) - **No compartas claves de API** en plataformas como Moltbook, ni siquiera en "mensajes privados" - **Revisa los permisos** que le otorgas al agente. No des acceso a credenciales que no necesita - **Monitorea el consumo de API**: un usuario reportó un gasto de $20 en una noche por una funcionalidad de recordatorios que quemaba ~120,000 tokens de contexto por verificación, a $0.75 cada una ## El contexto técnico: "vibe coding" y sus consecuencias Un aspecto que pocos artículos mencionan pero que es relevante para entender los problemas de seguridad: el creador Peter Steinberger ha descrito su filosofía de desarrollo como lo que hoy se llama **"vibe coding"**. El newsletter Pragmatic Engineer lo citó diciendo: *"I ship code I don't read"* (envío código que no leo). OpenClaw comenzó como un "proyecto de fin de semana" en noviembre de 2025, pensado originalmente para hacer vibe-coding en la PC enviando mensajes de texto desde el teléfono. La velocidad de desarrollo fue extraordinaria, pero el costo fue un software con acceso profundo al sistema operativo que no fue diseñado con seguridad como prioridad desde el inicio. Esto no invalida el proyecto (muchos proyectos open-source exitosos comenzaron así), pero sí explica por qué las vulnerabilidades son tan fundamentales: no son bugs menores sino defectos de diseño que reflejan una arquitectura construida para funcionalidad primero y seguridad después. ## ClawCon y el crecimiento de la comunidad El 4 de febrero de 2026 se celebró **ClawCon** en San Francisco, la primera conferencia presencial dedicada a OpenClaw. El evento es significativo por lo que representa: un proyecto que pasó de ser un experimento personal a tener una comunidad global lo suficientemente grande como para justificar una conferencia en menos de tres meses. La diferencia relevante con proyectos de infraestructura como Docker o Kubernetes es que OpenClaw no es un contenedor aislado: es un agente con acceso a tus credenciales, tu correo y tu sistema de archivos. ## Preguntas Frecuentes ### ¿Qué es OpenClaw y cuál es su relación con Clawdbot? OpenClaw es el nombre actual del proyecto que originalmente se llamó Clawdbot. Es un agente de IA open-source que se ejecuta localmente en tu dispositivo y se conecta a aplicaciones de mensajería como WhatsApp, Telegram y Discord para automatizar tareas reales. El nombre cambió dos veces (Clawdbot, Moltbot, OpenClaw) pero el proyecto y el equipo son los mismos. ### ¿Por qué Clawdbot cambió de nombre dos veces? El primer cambio (Clawdbot a Moltbot, 27 de enero de 2026) fue por una solicitud de marca registrada de Anthropic, ya que "Clawd" era demasiado similar a "Claude". El segundo cambio (Moltbot a OpenClaw, 30 de enero de 2026) fue porque el nombre Moltbot no resultaba natural ni fácil de recordar. ### ¿Es seguro instalar skills de ClawHub? No sin precaución. Investigadores encontraron 341 skills maliciosas de un total de 2,857 auditadas (12%). El directorio ClawHub no tiene revisión de código ni verificación de seguridad. Si vas a instalar una skill, revisa siempre el código fuente antes de ejecutarla. Usa Docker para sandboxing y mantén OpenClaw actualizado. ### ¿Qué es Moltbook y cómo se relaciona con OpenClaw? Moltbook es una red social separada donde los usuarios son exclusivamente agentes de IA (los humanos solo observan). Fue creada por Matt Schlicht usando un agente de OpenClaw. Aunque son proyectos independientes, Moltbook surgió del ecosistema de OpenClaw y comparten comunidad. Moltbook ha tenido problemas significativos de seguridad, incluyendo una base de datos expuesta con 1.5 millones de tokens de autenticación. ### ¿OpenClaw tiene una versión hosted o solo self-hosted? Ambas. Además de la instalación local tradicional (que ofrece máxima privacidad), ahora existe openclaw.ai, una plataforma hosted que permite usar el asistente sin configuración técnica. La versión hosted es más accesible pero implica ceder control sobre dónde se procesan tus datos. ### ¿Cuántas estrellas tiene OpenClaw en GitHub? A principios de febrero de 2026, OpenClaw superó las 147,000 estrellas en GitHub, con reportes de hasta 157,000 estrellas pocas semanas después de su lanzamiento. Esto lo convierte en uno de los proyectos open-source de más rápido crecimiento en la historia de la plataforma. ### ¿Qué fue ClawCon? ClawCon fue la primera conferencia presencial dedicada a OpenClaw, celebrada el 4 de febrero de 2026 en San Francisco. Marcó el paso del proyecto de experimento personal a plataforma con comunidad global organizada. ### ¿Puedo seguir usando la documentación de Clawdbot? Los antiguos dominios (clawd.bot, docs.clawd.bot) redirigen a los nuevos dominios de OpenClaw. La documentación actualizada está en el sitio oficial de openclaw.ai. Los comandos de instalación también han cambiado, así que si seguiste una guía anterior, revisa la documentación oficial para los pasos actualizados. ## Conclusión OpenClaw pasó de 0 a 147,000+ estrellas en menos de tres meses. La idea de un asistente de IA local, extensible y conectado a tus aplicaciones de mensajería tiene casos de uso concretos. Pero la velocidad del crecimiento ha superado la capacidad del proyecto para mantener estándares de seguridad adecuados. Tres CVEs en tres días, 341 skills maliciosas en un marketplace sin verificación, una red social con la base de datos abierta al mundo, y una filosofía de desarrollo que prioriza velocidad sobre seguridad son señales de alerta que no se pueden ignorar. Mi recomendación es la misma que en el [artículo original sobre Clawdbot](/post/clawdbot-asistente-ia-personal-open-source): si te interesa la productividad automatizada y tienes experiencia técnica, OpenClaw vale la pena probarlo. Pero hazlo con los ojos abiertos. Usa sandboxing, revisa las skills antes de instalarlas, no compartas credenciales en plataformas de terceros, y mantén el software actualizado. Los agentes de IA personales avanzan rápido. El desafío está en que la seguridad madure al mismo ritmo que las funcionalidades. --- ### Migrar al cloud: quién puede hacerlo por ti - URL: https://www.angelcruz.dev/post/migrar-servidores-al-cloud-sin-interrupciones - Markdown: https://www.angelcruz.dev/post/migrar-servidores-al-cloud-sin-interrupciones.md - Categoría: DevOps - Fecha: 2026-02-04 - Excerpt: Migrar al cloud sin riesgos es posible. Descubre quién puede migrar tu infraestructura a la nube de forma segura, transparente y sin interrupciones con Aitire. --- title: "Migrar al cloud: quién puede hacerlo por ti" excerpt: "Migrar al cloud sin riesgos es posible. Descubre quién puede migrar tu infraestructura a la nube de forma segura, transparente y sin interrupciones con Aitire." date: "2026-02-04T20:45:00.000Z" category: "DevOps" seo_title: "Migrar al cloud sin interrupciones con Aitire" seo_description: "Aitire migra tu infraestructura al cloud usando VPN en modo bridge, respetando IPs y moviendo datos fuera del horario laboral. Rollback sencillo y operación posterior en servidores virtuales NVMe." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Cuando una empresa decide dar el salto a la nube, la primera pregunta suele ser: **¿quién nos puede migrar al cloud?** No es solo mover servidores de un CPD a otro: impacta en la continuidad del negocio, la seguridad, los costes y tu capacidad de crecer. Tener claro el objetivo no basta; necesitas un partner que lleve tu infraestructura al cloud de forma **segura, controlada y transparente**, sin improvisar y sin afectar el día a día. En este artículo verás qué conviene evaluar al elegir quién te migra y por qué **Aitire** encaja para la migración de infraestructura a la nube. ## Por qué importa elegir bien quién te migra Migrar al cloud no es “subir servidores a internet”. Implica analizar tu infraestructura, entender las dependencias entre sistemas, planificar el movimiento de datos, garantizar la continuidad del servicio y validar que todo funcione igual o mejor. En entornos empresariales suele haber condicionantes clave: IPs que no pueden cambiar, licencias ya adquiridas, aplicaciones críticas que no admiten paradas largas o integraciones con sistemas locales. Un proveedor especializado en migración de infraestructura a la nube se encarga de todo el proceso: análisis, diseño de la arquitectura destino, ejecución y validación. El objetivo no es solo llegar al cloud, sino hacerlo sin sobresaltos y con garantías. Si una vez en la nube vas a operar sobre servidores virtuales cloud, conviene que quien te migra sea el mismo que te ofrece ese entorno. Evitas cambiar de proveedor más adelante, reduces la complejidad y tienes un solo contacto para infraestructura, soporte y evolución. ## Aitire: te migra y te aloja en su cloud **Aitire** se dedica a eso: ayudar a empresas a migrar sus sistemas al cloud de forma sencilla, rápida y eficiente, y ofrecer después un entorno estable sobre su propia nube. No se limitan a darte acceso a un panel; su enfoque es integral. Analizan tu infraestructura, física o virtual, identifican máquinas, servicios y dependencias, y crean una copia de todo el entorno en su cloud. Se encargan del traslado y de la puesta en marcha en cada fase. Cuando la migración está completada, su cloud alberga y responde las peticiones de tus servicios. Las IPs y la lógica de red suelen mantenerse coherentes con lo que tenías en local, de modo que los usuarios no perciben cambios. Esa continuidad define una buena migración de infraestructura a la nube: el negocio sigue funcionando mientras la tecnología evoluciona. ## Cómo lo hacen: migración transparente y reversible Aitire establece un túnel VPN en modo *bridge* entre tu sede y su cloud. Así las máquinas virtuales pueden coexistir en ambos lados con el mismo rango de IPs. Tus servidores locales y los del cloud quedan en una misma red lógica: dos servidores con IPs del mismo rango se comunican como si estuvieran en el mismo switch. Para aplicaciones y usuarios, no hay cambios perceptibles. El movimiento de datos se hace fuera del horario laboral. Suelen migrar alrededor de 1 TB por noche, repitiendo el proceso hasta completar todas las máquinas virtuales. La actividad diaria no se ve afectada. La migración es **reversible**. Si surge un imprevisto, el rollback es sencillo: apagar la máquina virtual migrada en el cloud y volver a encender la copia local. Eso reduce riesgos y da tranquilidad durante todo el proceso. Aitire respeta las IPs locales y permite reutilizar licencias de sistemas operativos o bases de datos que ya tengas, sin costes adicionales. Todo pensado para que la migración de infraestructura a la nube sea predecible, controlada y segura. ## Después de la migración: servidores virtuales cloud de Aitire Una vez migrado, el día a día se apoya en los servidores virtuales cloud de Aitire. Son VPS sobre su propia infraestructura, cada uno con su sistema operativo y gestión independiente. No hay “sabores” cerrados. Defines los recursos que necesitas (vCPU, RAM, almacenamiento) y los ajustas después según tu negocio. Los cambios se hacen en caliente, sin reiniciar el servidor. Incluyen almacenamiento de alto rendimiento (NVMe, SSD o SATA), transferencia ilimitada, firewall y VPNs bajo tu control, backups multidatacenter y soporte en castellano (8x5, ampliable a 24x7). Si necesitas mantener servicios en local (Active Directory, DFS, DNS, DHCP, NTP), Aitire puede desplegar un Edge Compute Server que gestionas como una máquina virtual más del mismo ecosistema. La migración de infraestructura a la nube y la operación posterior quedan en un solo proveedor. ## Qué ganas si es Aitire quien te migra Qué ganas desde el primer día: - **Sin costes de hardware:** dejas de invertir en servidores físicos, renovación de equipos, mantenimiento y repuestos. - **Menos riesgo ante catástrofes:** cortes, inundaciones o incendios dejan de ser una amenaza directa para tu infraestructura crítica. - **Menor factura energética:** al reducir equipos locales, baja el gasto en electricidad y refrigeración. - **Más espacio en oficina:** en muchos casos basta un armario mural para el equipamiento restante. - **Copias de seguridad en otro CPD:** tus backups en un datacenter distinto al de producción; tus datos protegidos y en línea con buenas prácticas y el Esquema Nacional de Seguridad cuando aplica. Todo ello se traduce en mayor estabilidad, previsibilidad de costes y capacidad de crecimiento. ## En resumen: quién puede migrarte al cloud La respuesta corta: **un proveedor especializado en migración y en cloud** que te acompañe de principio a fin y te ofrezca un entorno estable después. Aitire encaja en ese perfil: realiza la migración de infraestructura a la nube con metodología contrastada (VPN en modo bridge, respeto de IPs, migración fuera de horario, rollback sencillo) y te deja operando sobre servidores virtuales cloud flexibles, escalables y bien soportados. Si te estás preguntando “¿quién nos puede migrar al cloud?”, conviene elegir a quien no solo te lleve allí, sino que te ofrezca el entorno donde vas a operar después. Aitire hace ambas cosas y puede asesorarte según tu infraestructura y tus objetivos de negocio. **¿Quieres que revisen tu caso?** Cuéntales tu infraestructura y te propondrán la configuración que mejor se adapte a lo que necesitas. --- ### Ralph Loop: La Técnica que Revoluciona los Agentes de IA en 2026 - URL: https://www.angelcruz.dev/post/ralph-loop-revolucion-agentes-ia - Markdown: https://www.angelcruz.dev/post/ralph-loop-revolucion-agentes-ia.md - Categoría: Inteligencia Artificial - Fecha: 2026-01-26 - Excerpt: Descubre Ralph Loop, la metodología de Geoffrey Huntley que permite a los agentes de IA trabajar en tareas complejas sin límites de contexto, usando Git como memoria y reiniciando cada iteración con contexto fresco --- title: "Ralph Loop: La Técnica que Revoluciona los Agentes de IA en 2026" excerpt: "Descubre Ralph Loop, la metodología de Geoffrey Huntley que permite a los agentes de IA trabajar en tareas complejas sin límites de contexto, usando Git como memoria y reiniciando cada iteración con contexto fresco" date: "2026-01-26T01:08:24.000Z" category: "Inteligencia Artificial" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/6/01KFVNN63PW8X4T793JQSTE2Z4.png" seo_title: "Ralph Loop: Metodología para Agentes IA sin Límites 2026" seo_description: "Ralph Loop de Geoffrey Huntley: metodología que permite agentes IA trabajar sin límites de contexto usando Git. Guía completa 2026 con ejemplos prácticos." --- ¿Qué pasaría si pudieras dejar a un agente de IA trabajando en un proyecto complejo durante horas, sin preocuparte por la "contaminación de contexto" o que olvide lo que estaba haciendo? Eso es exactamente lo que propone Ralph Loop, una técnica que viene ganando tracción en la comunidad de desarrollo con IA desde finales de 2025. Acuñada por Geoffrey Huntley e inspirada en el personaje Ralph Wiggum de Los Simpson, la idea central es simple: en lugar de luchar contra las limitaciones de memoria de los modelos de IA, externalizarlas y convertirlas en parte del flujo de trabajo. ## ¿Qué es Ralph Loop? Ralph Loop (también conocido como "Ralph Wiggum Loop") no es un framework tradicional, sino una **metodología o patrón** para construir agentes de IA persistentes y autónomos, especialmente diseñados para tareas de programación complejas. La idea central es ejecutar un agente de IA en un **bucle continuo**, donde cada iteración comienza con un **contexto completamente fresco**, mientras que el progreso y el estado se preservan externamente a través del **sistema de archivos y el historial de Git**. En lugar de mantener una conversación interminable que acumula "ruido" en el contexto (lo que se conoce como "context rot" o "context pollution"), Ralph Loop reinicia la memoria del agente en cada ciclo, pero le proporciona toda la información necesaria a través de archivos de especificación, planes de implementación y commits de Git. ## ¿Cómo funciona? La implementación típica de Ralph Loop involucra: 1. **Un bucle bash simple**: Un script `while` que llama repetidamente a un agente de IA (como Claude Code, Amp, o herramientas como Goose) en modo "headless" hasta que se cumple un criterio de parada. 2. **Dos modelos de IA**: - **Worker (trabajador)**: Realiza el código y las modificaciones (ej: GPT-4o) - **Reviewer (revisor)**: Evalúa el trabajo y proporciona feedback (ej: Claude Sonnet 4) 3. **Memoria externalizada**: - La especificación y el plan de implementación se convierten en la **fuente de verdad** - El progreso se guarda en archivos como `progress.txt` o `prd.json` - **Git se convierte en la capa de memoria principal**: cada iteración hace commit de los cambios 4. **Contexto fresco en cada iteración**: En lugar de mantener todo el historial de conversación, solo se cargan los archivos de feedback y resúmenes del ciclo anterior. ### Ejemplo de flujo de trabajo ```bash // El script ralph-loop.sh orquesta todo el proceso while [ condición_no_cumplida ]; do // Fase de trabajo: el agente implementa cambios goose run ralph-work.yaml // Commit de cambios git add . && git commit -m "Feature implementation" // Fase de revisión: otro modelo evalúa el trabajo goose run ralph-review.yaml // Si el revisor dice "shipped", salir del loop if [ review_status == "shipped" ]; then break fi done ``` ## Ventajas sobre agentes tradicionales ### 1. **Elimina la contaminación de contexto** Los agentes tradicionales sufren cuando el contexto crece demasiado: información irrelevante, detalles obsoletos o simplemente demasiados datos intermedios pueden degradar el rendimiento, causar alucinaciones o generar respuestas incorrectas. Ralph Loop soluciona esto reiniciando el contexto en cada iteración, manteniendo solo lo esencial. ### 2. **Git como memoria persistente** En lugar de depender de la memoria interna del modelo, Ralph externaliza todo a Git. Esto significa: - El agente puede retomar tareas después de reinicios - Tienes un historial completo y auditable de cada cambio - La "memoria" es tan confiable como tu repositorio Git ### 3. **Tareas complejas sin límites de ventana de contexto** Proyectos que tomarían decenas de miles de tokens pueden ejecutarse indefinidamente, ya que cada iteración solo consume el contexto necesario para el siguiente paso. ### 4. **Validación rigurosa** Al separar el modelo trabajador del revisor, se evita que el agente genere "tests fáciles para sí mismo" o produzca código de baja calidad ("AI slop"). El revisor actúa como un control de calidad independiente. ## Mejores prácticas según Geoffrey Huntley Geoffrey Huntley enfatiza que cuando Ralph comete errores, **el problema suele estar en el prompt, no en la herramienta**. Su filosofía es "afinar Ralph como una guitarra" mediante: ### **Especificidad en los prompts** Instrucciones vagas llevan al agente a "bucles errantes". Define objetivos claros y verificables. ### **Límites definidos** Delimita explícitamente qué está dentro y fuera del alcance para evitar que el agente implemente funcionalidades no deseadas. ### **Ejemplos concretos** Proporciona ejemplos de entradas y salidas esperadas para que el agente aprenda del contexto. ### **Señales de salida apropiadas** Usa tanto indicadores de completitud como señales explícitas para asegurar que el loop se detenga cuando la tarea realmente esté terminada. ### **Monitoreo continuo** Para proyectos complejos, no abandones al agente: supervisa su progreso y ajusta según sea necesario. ### **Especificaciones a prueba de balas** El plan de implementación debe ser la fuente de verdad incuestionable para el agente. ## Herramientas y ecosistema Desde su popularización a finales de 2025, han surgido varias herramientas: - **Goose**: CLI que implementa Ralph Loop con recetas configurables - **Ralph TUI**: Interfaz de terminal que proporciona visibilidad en tiempo real, seguimiento de tareas y control sobre el proceso - **snarktank/ralph**: Repositorio de GitHub que demuestra un loop de agente autónomo basado en el patrón de Geoffrey Huntley - **[OpenClaw](/post/clawdbot-asistente-ia-personal-open-source)**: Asistente IA open-source que ejecuta tareas autónomas persistentes en tu dispositivo. Aunque usa un enfoque diferente (daemon siempre activo vs loops), comparte la filosofía de agentes que trabajan de forma independiente con supervisión humana estratégica - **[Context7](/post/context7-documentacion-actualizada-asistentes-codigo-ia)**: Servidor MCP que proporciona documentación actualizada de +1000 librerías. Esencial para Ralph Loop cuando trabajas con código, asegurando que cada iteración use ejemplos y APIs actualizadas sin depender de conocimiento obsoleto del modelo **Nota importante**: Algunos usuarios recomiendan **evitar el plugin oficial de Ralph de Anthropic**, ya que puede degradar el rendimiento al mantener cada loop dentro de la misma ventana de contexto, contradiciendo el beneficio de contexto fresco. ## Casos de uso reales Hay ejemplos documentados en la comunidad de: - Creación de navegadores basados en Electron desde cero - Reestructuración de código legacy sin perder el hilo - Desarrollo de funcionalidades que requieren cambios en múltiples archivos y componentes - Generación de documentación, tests o migraciones de forma automatizada ## Hacia la ejecución autónoma Una tendencia visible en la comunidad tech es el cambio de enfoque: **de prompting en tiempo real a crear especificaciones detalladas para ejecución autónoma**. Ralph Loop es una expresión concreta de esa dirección: un agente que trabaja de forma independiente, con supervisión humana en los puntos clave, no en cada paso. ## Conclusión Ralph Loop propone una forma distinta de trabajar con agentes de IA: en lugar de mantener conversaciones que acumulan ruido, externaliza la memoria en archivos y Git, y reinicia el contexto en cada iteración. Si estás construyendo con agentes de IA o explorando automatización de desarrollo, vale la pena revisar cómo funciona. Podés empezar con [Goose](https://github.com/block/goose) o explorar el [repositorio de snarktank/ralph](https://github.com/snarktank/ralph) para ver implementaciones de referencia. --- ### Clawdbot: Tu Asistente de IA Personal Open-Source - URL: https://www.angelcruz.dev/post/clawdbot-asistente-ia-personal-open-source - Markdown: https://www.angelcruz.dev/post/clawdbot-asistente-ia-personal-open-source.md - Categoría: OpenClaw - Fecha: 2026-01-26 - Excerpt: Descubre Clawdbot, el asistente de IA que ejecuta tareas reales en tu dispositivo: gestiona emails, calendarios, navega la web y automatiza tu vida digital con total privacidad. --- title: "Clawdbot: Tu Asistente de IA Personal Open-Source" excerpt: "Descubre Clawdbot, el asistente de IA que ejecuta tareas reales en tu dispositivo: gestiona emails, calendarios, navega la web y automatiza tu vida digital con total privacidad." date: "2026-01-26T00:50:40.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "OpenClaw" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/5/01KFVJFYG53S1S5DCMGREM3CC2.png" seo_title: "Clawdbot (Ahora OpenClaw): Asistente IA Open-Source — Guía 2026" seo_description: "Clawdbot —ahora OpenClaw— es el asistente IA open-source que automatiza tareas en tu PC. Guía 2026: qué es, +50 integraciones, instalación y comparativa con ChatGPT." --- > **Actualización (febrero 2026):** Clawdbot ahora se llama **OpenClaw** tras dos cambios de nombre por motivos de marca registrada. **Cronología:** Clawdbot (nov 2025) → Moltbot (27-30 enero 2026, solo 3 días) → OpenClaw (30 enero 2026-presente). El proyecto ha crecido enormemente: +147,000 estrellas en GitHub, una plataforma hosted, y una comunidad global. Lee el artículo completo: [De Clawdbot a OpenClaw: todo lo que pasó](/post/openclaw-de-clawdbot-a-plataforma-agentes-ia). > **¿Buscas cómo instalarlo?** Consulta nuestra [guía completa de instalación de OpenClaw](/post/como-instalar-openclaw-guia-completa) con instrucciones paso a paso para npm, Docker, Raspberry Pi y todas las integraciones (Teams, Cron, ElevenLabs). **OpenClaw** (anteriormente Clawdbot) es un framework open-source de agentes de IA diseñado para automatizar tareas en Slack, Discord, Teams y Telegram. Construido sobre Node.js y compatible con OpenAI, Claude y modelos locales, permite crear bots conversacionales que ejecutan comandos, procesan datos y gestionan flujos de trabajo sin código. El proyecto fue renombrado de Clawdbot a OpenClaw en enero 2026 debido a conflictos de marca. Si llegaste aquí buscando información sobre Clawdbot o Moltbot, este es su sucesor oficial. ## ¿Qué es Clawdbot (ahora OpenClaw)? **Clawdbot** (ahora llamado **OpenClaw**) es un asistente de IA open source que funciona como alternativa gratuita a ChatGPT, deployable en tu propia infraestructura (Raspberry Pi, Mac Mini, VPS o servidor local). Creado por Peter Steinberger, permite automatizar tareas reales, integrar con +50 aplicaciones y mantener privacidad total de tus datos mediante self-hosting. > **Nota sobre el nombre:** Este artículo usa "Clawdbot" (el nombre original y más buscado), pero el proyecto se llama oficialmente **OpenClaw** desde el 30 de enero de 2026. A diferencia de asistentes como ChatGPT o Claude en su versión web, Clawdbot se ejecuta localmente en tu propio dispositivo y no se reinicia con memoria limpia cada vez: mantiene contexto persistente, recuerda tus preferencias y puede actuar de forma proactiva. Funciona conectándose a aplicaciones de chat que ya usas. Envías un mensaje por WhatsApp o Telegram, y Clawdbot responde ejecutando acciones reales: leer y responder emails, agregar eventos al calendario, buscar información en la web, llenar formularios o ejecutar comandos en la terminal. ### Características principales - **100% open source y gratuito** - Solo pagas por APIs de LLM (Claude, GPT-4) - **Self-hosting completo** - Deploy en Raspberry Pi, Mac Mini, VPS o local - **Integración con múltiples LLMs** - Anthropic Claude, OpenAI GPT-4, modelos locales - **+50 integraciones listas** - Gmail, Calendar, WhatsApp, Telegram, Discord, Spotify, GitHub - **Automatización avanzada** - Cron jobs, webhooks, scripts, navegación web - **Privacidad total** - Tus datos nunca salen de tu servidor ### ¿Cuándo usar Clawdbot vs ChatGPT? | Factor | Clawdbot | ChatGPT Plus | |--------|----------|--------------| | **Costo** | Gratis (solo API ~$10-20/mes) | $20/mes | | **Privacidad** | Total control local | Cloud OpenAI | | **Customización** | Ilimitada (código abierto) | Limitada (GPTs) | | **Setup inicial** | 10-15 min técnico | 0 min (crear cuenta) | | **Integraciones** | +50 nativas + custom | Limitadas vía GPTs | | **Memoria persistente** | Sí, ilimitada | Sí, con límites | | **Ejecución de comandos** | Sí (terminal, scripts) | No (solo text) | | **Deploy** | Tu servidor/Raspberry Pi | Cloud OpenAI | **Usa Clawdbot si:** Necesitas privacidad, customización profunda, integraciones reales con tu sistema, o quieres control total de costos y datos. **Usa ChatGPT si:** Quieres simplicidad sin setup técnico, no necesitas integraciones profundas, o prefieres pagar mensual fijo sin gestionar infraestructura. ## Características principales **Integraciones con mensajería:** WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams y más. Puedes hablarle en chats individuales o grupos. **Control del sistema:** Acceso a archivos, ejecución de comandos shell, scripts y navegación web automatizada (con control total del navegador). **Memoria persistente:** Recuerda conversaciones anteriores y preferencias. Puede generar resúmenes diarios o informes automáticos. **Habilidades (skills) extensibles:** Más de 50 integraciones listas, como Gmail, Google Calendar, Spotify, Philips Hue, Obsidian, Todoist, GitHub o Whoop. La comunidad crea nuevas y el propio asistente puede generarlas por conversación. **Modo proactivo:** Usa cron jobs para tareas programadas, recordatorios o monitoreo (por ejemplo, alertas de emails urgentes). **Voz y multimedia:** Soporte para entrada/salida de voz (con ElevenLabs), transcripción y generación de imágenes o canvas interactivos. **Multi-agente:** Puedes tener varios asistentes simultáneos con nombres personalizados (como "Jarvis" o "Claudia"). ## Cómo instalarlo y configurarlo La instalación es relativamente sencilla para usuarios con algo de experiencia técnica: 1. **Requiere Node.js** (versión 22 o superior) 2. **Instalación rápida:** Ejecuta en la terminal: ```bash curl -fsSL https://clawd.bot/install.sh | bash ``` 3. **O instala vía npm:** ```bash npm i -g clawdbot clawdbot onboard ``` 4. **Configuración guiada:** El wizard te guía para configurar el "gateway" (el núcleo), canales de mensajería y habilidades iniciales. 5. **Seguridad opcional:** Puedes usar sandboxing (ejecutar agentes en contenedores Docker) y políticas de emparejamiento para mensajes directos. La documentación oficial está en [docs.clawd.bot](https://docs.clawd.bot), y hay una comunidad activa en Discord. ## Experiencias de usuarios Entre los casos documentados públicamente: - **Compra de auto automatizada:** Un usuario reportó haber delegado la búsqueda de inventarios y contacto con concesionarios. - **Resúmenes matutinos personalizados:** Automatización de resúmenes con emails, calendario y noticias, enviados por voz. - **Automatización del hogar:** Control de luces inteligentes, transcripción de reuniones y generación de informes. MacStories publicó una reseña destacando sus capacidades de ejecución local y la posibilidad de extenderlo con nuevas habilidades. ## Ventajas y consideraciones ### Ventajas - **Máxima privacidad:** Todo local, tus datos no salen de tu dispositivo - **Altamente personalizable y extensible:** Adapta el asistente a tus necesidades exactas - **Reemplaza herramientas pagas:** Alternativa open-source a servicios como Zapier ([lee nuestra comparativa OpenClaw vs Zapier](/post/openclaw-vs-zapier-cual-elegir-automatizacion)) - **Comunidad activa:** Open-source en [GitHub](https://github.com/clawdbot/clawdbot) con contribuciones constantes ### Consideraciones - **Configuración técnica:** Requiere configuración inicial técnica (no es "plug and play" para principiantes) - **Costos de API:** Si usas modelos como Claude Opus, el consumo de tokens puede ser alto (y costoso) - **Permisos del sistema:** Otorga acceso profundo al sistema, por lo que hay que gestionar permisos con cuidado (aunque incluye medidas de seguridad como pairing y sandboxing) ## Conclusión Clawdbot (ahora OpenClaw) es un asistente de IA local que puede automatizar tareas reales a través de mensajería, manteniendo los datos en tu propio servidor. A diferencia de asistentes web como Siri o Google Assistant, ejecuta acciones concretas y mantiene contexto persistente entre sesiones. **Puntos clave:** - Asistente de IA local con privacidad total - Más de 50 integraciones listas para usar - Automatiza tareas reales, no solo conversaciones Si te interesa la productividad automatizada y no te asusta un poco de configuración, vale la pena probarlo. Puedes empezar en [openclaw.ai](https://openclaw.ai) o el [repositorio en GitHub](https://github.com/openclaw/openclaw). **Recursos oficiales:** - Sitio web: [openclaw.ai](https://openclaw.ai) - GitHub: [github.com/openclaw/openclaw](https://github.com/openclaw/openclaw) - Documentación: [docs.openclaw.ai](https://docs.openclaw.ai) **Potencia OpenClaw con estas herramientas:** - [Context7](/post/context7-documentacion-actualizada-asistentes-codigo-ia): Documentación actualizada de +1000 librerías vía MCP para que OpenClaw genere código sin APIs obsoletas - [Ralph Loop](/post/ralph-loop-revolucion-agentes-ia): Metodología para trabajar con agentes IA sin límites de contexto, ideal para tareas complejas con OpenClaw --- ### Revalidación On-Demand en Next.js: Invalidar Caché con revalidateTag y revalidatePath - URL: https://www.angelcruz.dev/post/revalidacion-cache-nextjs - Markdown: https://www.angelcruz.dev/post/revalidacion-cache-nextjs.md - Categoría: Next.js - Fecha: 2026-01-17 - Excerpt: Aprende cómo implementar revalidación de caché en Next.js usando revalidateTag y revalidatePath con webhooks para mantener tu contenido siempre actualizado sin sacrificar rendimiento. --- title: "Revalidación On-Demand en Next.js: Invalidar Caché con revalidateTag y revalidatePath" excerpt: "Aprende cómo implementar revalidación de caché en Next.js usando revalidateTag y revalidatePath con webhooks para mantener tu contenido siempre actualizado sin sacrificar rendimiento." date: "2026-01-17T12:55:27.000Z" category: "Next.js" seo_title: "Revalidación on-demand en Next.js con revalidateTag y webhook" seo_description: "Configura revalidación de caché on-demand en Next.js usando revalidateTag con expire: 0 y webhooks autenticados. Estrategia de 30 días de caché con actualización inmediata al publicar contenido." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/4/01KF4MJMCWDRH9M8H7J0P25GP9.png" --- Cuando trabajas con Next.js y aplicaciones que consumen datos de APIs externas, mantener el contenido actualizado se convierte en un desafío. Tienes contenido que cambia en tu backend, pero tu frontend sigue mostrando datos en caché que ya no son relevantes. La solución tradicional sería reducir el tiempo de caché o deshabilitarlo completamente, pero eso afecta significativamente el rendimiento. La alternativa es implementar **revalidación on-demand** o revalidación bajo demanda, un mecanismo que permite invalidar el caché de forma programática cuando el contenido cambia. En este artículo exploraremos cómo implementar un sistema de revalidación de caché en Next.js que se actualice automáticamente cuando cambies contenido en tu backend, sin sacrificar el rendimiento. ## ¿Qué es la Revalidación de Caché? La revalidación de caché es un mecanismo que permite invalidar el caché de Next.js de forma programática, en lugar de esperar a que expire el tiempo de revalidación configurado. Next.js ofrece dos formas principales de hacer esto: 1. **`revalidatePath`**: Invalida el caché de una ruta específica (por ejemplo, `/post/mi-articulo`) 2. **`revalidateTag`**: Invalida el caché basado en etiquetas que asignas a tus peticiones fetch La segunda opción es más flexible y potente, ya que puedes etiquetar múltiples peticiones con la misma etiqueta y invalidarlas todas de una vez. ## El Problema y la Estrategia Imagina este escenario: - Tienes un blog con artículos que se publican desde un CMS o backend - Los artículos se muestran en varias páginas: la página principal, listado de posts, página individual, sitemap, feed RSS - Quieres cachear todo agresivamente para mejorar el rendimiento (30 días, por ejemplo) - Pero cuando publicas un nuevo artículo, quieres que aparezca inmediatamente Sin revalidación, tendrías que esperar 30 días o reducir el tiempo de caché a minutos, lo cual no es ideal. ### ¿Por qué un Caché de 30 Días? Puede parecer contradictorio usar un caché tan largo cuando necesitas contenido actualizado. Sin embargo, esta estrategia es correcta y recomendada por varias razones: **Protección de la API**: Un caché largo reduce drásticamente la cantidad de peticiones a tu API backend. Esto protege tu servidor de sobrecarga, especialmente en sitios con alto tráfico. Sin caché, cada visita generaría múltiples peticiones a la API. **Rendimiento óptimo**: Con un caché de 30 días, Next.js puede servir contenido desde su caché interno sin necesidad de consultar la API en cada solicitud. Esto resulta en tiempos de respuesta extremadamente rápidos. **Revalidación bajo demanda**: La clave está en combinar el caché largo con revalidación on-demand. Cuando el contenido cambia, el webhook invalida el caché inmediatamente, forzando a Next.js a obtener datos frescos en la próxima solicitud. Esto proporciona lo mejor de ambos mundos: rendimiento y actualización inmediata. **Costo y escalabilidad**: Menos peticiones a la API significa menos costo de infraestructura y mejor escalabilidad. Tu backend puede manejar más tráfico sin necesidad de escalar recursos. En resumen, el caché de 30 días no es un problema cuando tienes revalidación on-demand. Es una estrategia de optimización que protege tu API mientras garantiza contenido actualizado cuando es necesario. ## Implementación ### Paso 1: Configurar Tags de Caché en tus Fetches Primero, necesitas etiquetar todas tus peticiones fetch con tags que puedas referenciar después. Esto es crítico: si una función fetch no tiene tags, no se invalidará cuando el webhook revalide el caché. Aquí tienes ejemplos de cómo etiquetar tus fetches: ```typescript // lib/api.ts export async function getPosts(): Promise { const data = await fetchAPI('/post', { next: { revalidate: 2592000, // 30 días tags: ['posts', 'posts-list'], // Etiquetas para revalidación }, }) return data.data.map(mapApiPostToArticle) } export const getPostBySlug = cache(async (slug: string): Promise
=> { const data = await fetchAPI(`/post/${slug}`, { next: { revalidate: 2592000, // 30 días tags: ['posts', `post-${slug}`], // Etiqueta general + específica }, }) return mapApiPostToArticle(data.data) }) // IMPORTANTE: Todas las funciones de fetch deben estar envueltas con React.cache() // para deduplicación por request y deben tener tags para poder ser invalidadas // IMPORTANTE: No olvides agregar tags a TODAS las funciones que usan caché export async function getAllPostSlugs(): Promise { const data = await fetchAPI('/post', { next: { revalidate: 2592000, tags: ['posts', 'posts-list'], // Sin esto, no se invalidará el caché }, }) return data.data.map(post => post.slug) } export const getCategories = cache(async (): Promise => { const data = await fetchAPI('/categories', { next: { revalidate: 2592000, tags: ['categories'], // Tags para categorías }, }) return data.data.map(mapApiCategoryToCategory) }) ``` **Error común**: Olvidar agregar tags a funciones como `getAllPostSlugs()` o `getCategories()`. Si una función no tiene tags, el webhook no podrá invalidar su caché, y seguirás viendo datos antiguos durante 30 días. **Nota importante**: Asegúrate de que tu función `fetchAPI` acepte correctamente las opciones `next`. Si estás usando TypeScript, necesitarás extender el tipo: ```typescript // lib/api-client.ts interface NextFetchOptions { next?: { revalidate?: number | false tags?: string[] } } type FetchAPIOptions = RequestInit & NextFetchOptions export async function fetchAPI( url: string, options?: FetchAPIOptions ): Promise { // ... tu implementación const response = await fetch(fullUrl, { ...options, // Esto incluye las opciones 'next' headers: { 'Content-Type': 'application/json', ...options?.headers, }, }) // ... } ``` ### Paso 2: Crear el Endpoint de Revalidación Ahora crea un endpoint de API que reciba las notificaciones de tu backend: ```typescript // app/api/revalidate/route.ts import { revalidatePath, revalidateTag } from 'next/cache' import { NextRequest, NextResponse } from 'next/server' type ResourceType = 'post' | 'category' | 'page' type ResourceAction = 'created' | 'updated' | 'deleted' | 'published' interface ResourceMetadata { type: ResourceType id: number action: ResourceAction timestamp: number slug?: string categorySlug?: string } interface RevalidatePayload { token: string paths?: string[] resource?: ResourceMetadata } export async function POST(request: NextRequest) { try { const body: RevalidatePayload = await request.json() // 1. Validar el token de seguridad const expectedToken = process.env.REVALIDATE_TOKEN if (!expectedToken || body.token !== expectedToken) { return NextResponse.json( { message: 'Invalid token' }, { status: 401 } ) } // 2. Generar tags y paths a revalidar let tagsToRevalidate: string[] = [] const pathsToRevalidate: string[] = [] if (body.resource) { const { type, slug, action } = body.resource // Generar tags según el tipo de recurso switch (type) { case 'post': tagsToRevalidate.push('posts', 'posts-list', 'posts-paginated') if (slug) { tagsToRevalidate.push(`post-${slug}`) } break case 'category': tagsToRevalidate.push('categories') // Revalidar tags de categorías tagsToRevalidate.push('posts', 'posts-list', 'posts-paginated') // Categorías afectan posts break } // Generar paths según el tipo de recurso pathsToRevalidate.push('/') // Siempre revalidar home switch (type) { case 'post': if (slug) { pathsToRevalidate.push(`/post/${slug}`) } pathsToRevalidate.push('/post', '/categorias') if (body.resource.categorySlug) { pathsToRevalidate.push(`/categorias/${body.resource.categorySlug}`) } pathsToRevalidate.push('/sitemap.xml', '/feed.xml') break case 'category': if (slug) { pathsToRevalidate.push(`/categorias/${slug}`) } pathsToRevalidate.push('/categorias', '/post') break } } // Si no hay resource pero hay paths relacionados con posts, inferir tags // Esto asegura que el caché se invalide incluso si el backend no envía metadata if (tagsToRevalidate.length === 0) { const paths = body.paths || pathsToRevalidate const hasPostPaths = paths.some(path => path === '/post' || path.startsWith('/post/') || path === '/' || path.includes('sitemap') || path.includes('feed') ) const hasCategoryPaths = paths.some(path => path === '/categorias' || path.startsWith('/categorias/') ) if (hasPostPaths) { tagsToRevalidate = ['posts', 'posts-list', 'posts-paginated'] console.log('[Revalidate] No resource provided, inferring post tags from paths') } if (hasCategoryPaths) { if (!tagsToRevalidate.includes('categories')) { tagsToRevalidate.push('categories') } // Las categorías afectan posts if (!tagsToRevalidate.includes('posts')) { tagsToRevalidate.push('posts', 'posts-list', 'posts-paginated') } } } // 3. Revalidar tags (crítico para invalidar el caché de 30 días) const revalidatedTags: string[] = [] const failedTags: string[] = [] if (tagsToRevalidate.length === 0) { console.warn('[Revalidate] No cache tags to revalidate - fetch cache may not be invalidated!') } for (const tag of tagsToRevalidate) { try { // IMPORTANTE: Usar { expire: 0 } para invalidación inmediata // 'max' solo marca como stale pero no invalida inmediatamente // expire: 0 fuerza la expiración inmediata para que se obtengan datos frescos en la próxima solicitud revalidateTag(tag, { expire: 0 }) revalidatedTags.push(tag) if (process.env.NODE_ENV === 'development') { console.log(`[Revalidate] Successfully revalidated tag: ${tag}`) } } catch (error) { console.error(`[Revalidate] Error revalidating tag ${tag}:`, error) failedTags.push(tag) } } // 4. Revalidar paths (si se proporcionaron explícitamente o se generaron) // IMPORTANTE: Revalidar tanto 'page' como 'layout' para asegurar invalidación completa const paths = body.paths || pathsToRevalidate const revalidatedPaths: string[] = [] for (const path of paths) { try { // Para rutas dinámicas y estáticas, revalidar tanto page como layout const isDynamicRoute = path.match(/^\/[^/]+\/[^/]+$/) && !path.endsWith('.xml') && path !== '/' if (isDynamicRoute || !path.endsWith('.xml')) { // Rutas dinámicas y estáticas: revalidar page y layout revalidatePath(path, 'page') revalidatePath(path, 'layout') } else { // Rutas especiales como /feed.xml revalidatePath(path) } revalidatedPaths.push(path) } catch (error) { console.error(`Error revalidating path ${path}:`, error) } } return NextResponse.json({ revalidated: revalidatedPaths.length > 0 || revalidatedTags.length > 0, paths: revalidatedPaths, tags: revalidatedTags.length > 0 ? revalidatedTags : undefined, failedTags: failedTags.length > 0 ? failedTags : undefined, }) } catch (error) { console.error('[Revalidate] Error:', error) return NextResponse.json( { message: 'Error revalidating', error: String(error) }, { status: 500 } ) } } ``` ### Paso 3: Configurar el Token de Seguridad Crea una variable de entorno para el token: ```bash REVALIDATE_TOKEN=tu_token_secreto_super_seguro_aqui // .env.local ``` **Importante**: Este token debe ser el mismo que uses en tu backend para autenticar las peticiones al endpoint de revalidación. ## Cómo Funciona el Flujo Completo Una vez que tienes configurado el endpoint de revalidación, el flujo es el siguiente: 1. **Tu backend/CMS dispara un webhook** a `/api/revalidate` cuando el contenido cambia 2. **El endpoint de Next.js valida el token** de seguridad 3. **Se procesan los datos del recurso** y se generan las tags y paths a revalidar 4. **Se invalidan las tags y paths** correspondientes usando `revalidateTag()` y `revalidatePath()` 5. **En la próxima solicitud**, Next.js detecta que el caché fue invalidado y obtiene datos frescos de la API 6. **Los usuarios ven el contenido actualizado** automáticamente sin necesidad de esperar a que expire el tiempo de caché El formato del payload que espera el endpoint es: ```typescript { "token": "tu_token_secreto", "resource": { "type": "post", "id": 123, "action": "published", "timestamp": 1734567890, "slug": "mi-articulo", "categorySlug": "laravel" } } ``` También puedes proporcionar paths explícitos si prefieres tener control total: ```typescript { "token": "tu_token_secreto", "paths": ["/", "/post", "/post/mi-articulo"], "resource": { "type": "post", "id": 123, "action": "published" } } ``` ## Mejores Prácticas ### 1. Usa Tags Específicos y Generales Combina tags generales con tags específicos: ```typescript tags: ['posts', 'posts-list', `post-${slug}`] ``` Esto te permite: - Invalidar todos los posts con `revalidateTag('posts')` - Invalidar solo un post específico con `revalidateTag('post-mi-articulo')` ### 2. Genera Paths y Tags Automáticamente En lugar de tener que especificar manualmente todos los paths en cada webhook, genera los paths automáticamente basándote en el tipo de recurso. Además, el webhook puede inferir tags desde los paths si el backend no envía metadata: ```typescript function generateRevalidationPaths(resource: ResourceMetadata): string[] { const paths: string[] = ['/'] // Siempre revalidar home if (resource.type === 'post' && resource.slug) { paths.push(`/post/${resource.slug}`) paths.push('/post', '/categorias') if (resource.categorySlug) { paths.push(`/categorias/${resource.categorySlug}`) } paths.push('/sitemap-posts.xml', '/feed.xml', '/sitemap.xml') } return paths } // Si no hay resource, inferir tags desde paths if (tagsToRevalidate.length === 0) { const hasPostPaths = paths.some(path => path === '/post' || path.startsWith('/post/') || path === '/' ) if (hasPostPaths) { tagsToRevalidate = ['posts', 'posts-list', 'posts-paginated'] } } ``` Esto asegura que el caché se invalide incluso si el backend no envía el campo `resource` en el webhook. ### 3. Maneja Errores Gracefully El webhook puede fallar por varias razones (red, timeout, etc.). En tu endpoint, asegúrate de manejar errores y devolver respuestas claras: ```typescript try { // ... revalidación ... } catch (error) { console.error('[Revalidate] Error:', error) return NextResponse.json( { message: 'Error revalidating', error: error instanceof Error ? error.message : String(error) }, { status: 500 } ) } ``` También es buena práctica que tu backend tenga un sistema de reintentos en caso de que el webhook falle. ### 4. Logging para Debugging Agrega logging estructurado para poder debuggear problemas. Usa `after()` de Next.js para hacer el logging de forma asíncrona y no bloquear la respuesta: ```typescript import { after } from 'next/server' // ... después de revalidar ... after(async () => { const logData = { paths: revalidatedPaths, tags: revalidatedTags.length > 0 ? revalidatedTags : undefined, failed: failedPaths.length > 0 ? failedPaths : undefined, failedTags: failedTags.length > 0 ? failedTags : undefined, resource: resource?.type || undefined, action: resource?.action || undefined, } const logMessage = { event: 'cache_revalidated', ...logData } if (process.env.NODE_ENV === 'production') { console.log(JSON.stringify(logMessage)) } else { console.log('[Revalidate] Cache revalidated:', JSON.stringify(logMessage, null, 2)) } }) ``` Esto te ayudará a identificar si las tags se están revalidando correctamente y si hay algún problema con el webhook. ## Invalidación de Vercel Edge Cache Vercel Edge Cache es la capa de caché CDN de Vercel que se encuentra delante de tu aplicación Next.js. Es importante entender su relación con el caché de Next.js: - **Caché de Next.js**: Es el caché interno de Next.js que se invalida con `revalidateTag()` y `revalidatePath()`. Este es el caché principal que controla qué datos se obtienen de tu API. - **Vercel Edge Cache**: Es el caché del CDN de Vercel que almacena respuestas completas de páginas. Este caché está delante de Next.js y puede servir contenido sin llegar a tu aplicación. ### ¿Es necesario invalidar Vercel Edge Cache? La invalidación de Vercel Edge Cache es **opcional pero altamente recomendada**: **Sin invalidación de Edge Cache**: Next.js seguirá funcionando correctamente. Cuando `revalidateTag()` invalida el caché de Next.js, la próxima solicitud que llegue a Next.js obtendrá datos frescos. Sin embargo, si Vercel Edge Cache tiene una copia en caché de la página completa, puede seguir sirviendo contenido antiguo desde el CDN sin llegar a Next.js. **Con invalidación de Edge Cache**: Garantizas que tanto el caché de Next.js como el caché del CDN se invalidan simultáneamente. Esto asegura que los usuarios siempre vean contenido actualizado, independientemente de si la solicitud se sirve desde el CDN o desde Next.js. **Recomendación**: Si tienes acceso a las credenciales de Vercel (`VERCEL_TOKEN` y `VERCEL_PROJECT_ID`), es recomendable invalidar también el Edge Cache para una experiencia de usuario óptima. Si no tienes estas credenciales o prefieres simplificar, la aplicación seguirá funcionando correctamente, pero puede haber un pequeño retraso hasta que el Edge Cache expire naturalmente. ### Implementación ```typescript // Invalidar Vercel Edge Cache // Esto asegura que el CDN también invalida su caché, no solo Next.js if (revalidatedTags.length > 0 && process.env.VERCEL_TOKEN && process.env.VERCEL_PROJECT_ID) { try { const projectId = process.env.VERCEL_PROJECT_ID const teamId = process.env.VERCEL_TEAM_ID const vercelApiUrl = teamId ? `https://api.vercel.com/v1/edge-cache/invalidate-by-tags?projectIdOrName=${projectId}&teamId=${teamId}` : `https://api.vercel.com/v1/edge-cache/invalidate-by-tags?projectIdOrName=${projectId}` const response = await fetch(vercelApiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VERCEL_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ tags: revalidatedTags, target: 'production', }), }) if (!response.ok) { const errorText = await response.text() console.error('[Revalidate] Failed to invalidate Vercel Edge Cache:', errorText) } } catch (error) { // No fallar toda la revalidación si falla la invalidación del edge cache console.error('[Revalidate] Error invalidating Vercel Edge Cache:', error) } } ``` **Variables de entorno necesarias:** - `VERCEL_TOKEN`: Token de API de Vercel - `VERCEL_PROJECT_ID`: ID del proyecto en Vercel - `VERCEL_TEAM_ID`: (Opcional) ID del equipo si el proyecto pertenece a un equipo ## Problemas Comunes y Soluciones ### "La revalidación no funciona - el caché de 30 días no se invalida" **Causa 1**: Las tags no coinciden exactamente. **Solución**: Asegúrate de que las tags que usas en `revalidateTag()` sean exactamente las mismas que usas en tus fetches. Un espacio extra o una diferencia de mayúsculas hará que no funcione. **Causa 2**: Alguna función fetch no tiene tags de caché. **Solución**: Revisa que todas tus funciones que usan `revalidate: 2592000` tengan tags. Si una función como `getAllPostSlugs()` o `getCategories()` no tiene tags, el webhook no podrá invalidar su caché. **Causa 3**: El backend no envía el campo `resource` en el webhook. **Solución**: El webhook ahora infiere tags automáticamente desde los paths que se están revalidando. Si revalidas `/post` o paths relacionados, automáticamente revalidará las tags `['posts', 'posts-list', 'posts-paginated']`. Sin embargo, es mejor que el backend siempre envíe el campo `resource` con el `slug` para revalidar tags específicas como `post-${slug}`. **Causa 4**: Estás usando `revalidateTag(tag, 'max')` en lugar de `revalidateTag(tag, { expire: 0 })`. **Solución**: `'max'` solo marca el caché como stale pero no lo invalida inmediatamente. Usa `{ expire: 0 }` para forzar la invalidación inmediata del caché. **Causa 5**: No estás invalidando Vercel Edge Cache (opcional pero recomendado). **Solución**: Next.js `revalidateTag` solo invalida el caché de Next.js, no el caché de Vercel Edge Cache (CDN). Si bien la invalidación de Edge Cache es opcional (Next.js seguirá funcionando correctamente), es altamente recomendable invalidar también el caché del CDN para garantizar que los usuarios vean contenido actualizado inmediatamente, incluso si la solicitud se sirve desde el CDN. Si tienes acceso a `VERCEL_TOKEN` y `VERCEL_PROJECT_ID`, asegúrate de llamar también a la API de Vercel para invalidar el caché del CDN. ### "Los componentes del cliente no se actualizan" **Causa**: Los componentes del cliente hacen fetch directamente desde el navegador, no desde el servidor. **Solución**: - Los Server Components se actualizarán automáticamente - Para Client Components, puedes implementar polling o usar `router.refresh()` después de detectar cambios ### "El webhook tarda mucho en responder" **Causa**: Estás haciendo operaciones síncronas pesadas en el endpoint. **Solución**: Usa `after()` de Next.js para hacer el logging de forma asíncrona: ```typescript import { after } from 'next/server' // ... revalidación ... after(async () => { // Logging asíncrono que no bloquea la respuesta console.log('Revalidation completed') }) ``` ## Conclusión La revalidación de caché on-demand es una herramienta poderosa que permite tener lo mejor de ambos mundos: caché agresivo para rendimiento y actualizaciones inmediatas cuando el contenido cambia. ### Resumen de la Estrategia Un caché de 30 días puede parecer excesivo, pero es la estrategia correcta cuando se combina con revalidación on-demand: - **Protege tu API**: Reduce drásticamente las peticiones al backend, mejorando la escalabilidad y reduciendo costos. - **Rendimiento óptimo**: Next.js puede servir contenido desde caché sin consultar la API en cada solicitud. - **Actualización inmediata**: El webhook invalida el caché cuando el contenido cambia, forzando a Next.js a obtener datos frescos en la próxima solicitud. Esta combinación proporciona rendimiento de caché largo con la frescura de actualización inmediata. ### Checklist de Implementación 1. Etiqueta todas tus fetches con `tags` para poder referenciarlos después (no olvides ninguna función) 2. Envuelve tus funciones de fetch con `React.cache()` para deduplicación por request 3. Crea un endpoint `/api/revalidate` que reciba webhooks y valide el token 4. Genera automáticamente las tags y paths a revalidar basándote en el tipo de recurso 5. Incluye lógica de inferencia para revalidar tags incluso si el backend no envía metadata completa 6. Invalida el caché usando `revalidateTag(tag, { expire: 0 })` para invalidación inmediata (no uses `'max'`) 7. Revalida paths con `revalidatePath(path, 'page')` y `revalidatePath(path, 'layout')` para invalidación completa 8. Invalida también Vercel Edge Cache (opcional pero recomendado) usando la API de Vercel si tienes acceso a las credenciales 9. Agrega logging estructurado para facilitar el debugging ### Puntos Clave a Recordar - Si una función fetch no tiene tags, el webhook no podrá invalidar su caché. Asegúrate de revisar todas tus funciones que usan `revalidate: 2592000` y agregarles tags correspondientes. - Usa `{ expire: 0 }` en `revalidateTag`, no `'max'`, para invalidación inmediata del caché. - Revalida tanto `'page'` como `'layout'` para asegurar invalidación completa de rutas. - La invalidación de Vercel Edge Cache es opcional pero recomendada para una experiencia óptima. ## Recursos Adicionales - [Documentación oficial de Next.js sobre revalidación](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) - [Next.js Data Fetching](https://nextjs.org/docs/app/building-your-application/data-fetching) --- ### Detecta ahorros ocultos en tu cuenta de DigitalOcean en 30 segundos — gratis y sin registro - URL: https://www.angelcruz.dev/post/herramienta-gratuita-optimizar-costos-digitalocean - Markdown: https://www.angelcruz.dev/post/herramienta-gratuita-optimizar-costos-digitalocean.md - Categoría: Herramientas - Fecha: 2026-01-16 - Excerpt: CloudSaver analiza tu DigitalOcean gratis en 30 segundos: detecta recursos inactivos y ahorra 10-40% en tu factura mensual sin comprometer seguridad. --- title: "Detecta ahorros ocultos en tu cuenta de DigitalOcean en 30 segundos — gratis y sin registro" excerpt: "CloudSaver analiza tu DigitalOcean gratis en 30 segundos: detecta recursos inactivos y ahorra 10-40% en tu factura mensual sin comprometer seguridad." date: "2026-01-16T22:51:44.000Z" category: "Herramientas" seo_title: "CloudSaver: audita tu DigitalOcean gratis y ahorra entre 10-40%" seo_description: "CloudSaver analiza tu infraestructura de DigitalOcean en 30 segundos con token de solo lectura. Detecta 11 tipos de desperdicio y estima el ahorro mensual sin almacenar datos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ## Introducción Gestionar infraestructura en la nube es rápido y flexible, pero también tiene una desventaja clara: es muy fácil olvidar recursos activos que siguen generando costos. En DigitalOcean, esto se manifiesta en droplets apagados, volúmenes huérfanos, snapshots antiguos o balanceadores de carga sin tráfico. Este artículo explica el problema del desperdicio en la nube, cómo funciona CloudSaver, qué tipos de recursos analiza y por qué puede ayudarte a reducir tus costos entre un 10% y un 40% de forma segura. ## El Problema del Desperdicio en la Nube El desperdicio en la nube es un problema ampliamente documentado. Estudios del sector estiman que entre el 30% y el 35% del gasto total en la nube corresponde a recursos infrautilizados o completamente inactivos. En DigitalOcean, los casos más frecuentes incluyen: - Droplets apagados que siguen facturándose - Volúmenes de almacenamiento desvinculados - Snapshots antiguos o duplicados - Load balancers sin uso real - Recursos sobredimensionados respecto a su carga real Identificar estos problemas manualmente implica navegar por múltiples secciones del panel, revisar métricas, cruzar precios y tomar decisiones con información incompleta. En cuentas medianas o grandes, este proceso puede llevar horas. ## ¿Qué es CloudSaver? CloudSaver es una aplicación web gratuita que realiza una auditoría integral de tu infraestructura de DigitalOcean. Analiza recursos, métricas y precios actuales para detectar desperdicio y oportunidades de optimización de costos. ### Características principales - Análisis de 11 tipos distintos de desperdicio - Auditoría completa en menos de 30 segundos - Uso exclusivo de tokens de solo lectura - Sin registro ni almacenamiento de datos - Recomendaciones claras con estimaciones de ahorro - Cálculos basados en precios actualizados de DigitalOcean > El objetivo no es automatizar eliminaciones, sino proporcionar información precisa para que tomes decisiones informadas. ## Cómo Funciona CloudSaver ### 1. Token de API de solo lectura El usuario genera un token de API con permisos de lectura. Esto garantiza que CloudSaver no pueda modificar, crear ni eliminar recursos. ### 2. Recolección de recursos CloudSaver obtiene información de droplets, volúmenes, snapshots, bases de datos, load balancers e IPs reservadas utilizando la API oficial de DigitalOcean. ### 3. Análisis concurrente Once analizadores especializados se ejecutan en paralelo para detectar patrones de desperdicio y subutilización. ### 4. Reporte inmediato El resultado incluye: - Costo mensual estimado - Ahorro potencial en USD - Porcentaje de optimización posible - Recomendaciones accionables con nivel de confianza ## Los 11 Analizadores de Optimización ### 1. Droplets apagados (zombie) Detecta servidores apagados que siguen generando cargos completos. ### 2. Volúmenes huérfanos Identifica volúmenes no conectados a ningún droplet. ### 3. Snapshots antiguos Señala snapshots con más de 30 días sin uso evidente. ### 4. Snapshots duplicados Detecta copias redundantes creadas en periodos cortos. ### 5. Backups redundantes Encuentra droplets con backups automáticos y snapshots manuales simultáneos. ### 6. Droplets sobredimensionados Analiza métricas de CPU y memoria para recomendar downgrades. ### 7. Bases de datos sobredimensionadas Evalúa el uso real frente al tamaño contratado. ### 8. Consolidación de droplets Sugiere combinar múltiples droplets pequeños en menos instancias. ### 9. Optimización por región Detecta configuraciones regionales potencialmente ineficientes. ### 10. Load balancers inactivos Identifica balanceadores sin droplets o con uso injustificado. ### 11. Volúmenes grandes sin uso efectivo Detecta almacenamiento costoso ligado a recursos inactivos. ## Privacidad y Seguridad CloudSaver fue diseñado bajo el principio de mínima confianza: - Tokens de solo lectura - No se almacenan tokens ni resultados - No hay cuentas de usuario - No existe base de datos persistente - Análisis efímero y aislado Esto elimina el riesgo de filtraciones y reduce la superficie de ataque. ## Arquitectura Técnica CloudSaver está construido con: - Next.js y TypeScript - API oficial de DigitalOcean - Procesamiento concurrente - Caché LRU en memoria - Cálculo de costos con catálogo completo de precios El sistema utiliza métricas históricas para evitar recomendaciones basadas en picos temporales. ## ¿Quién Debería Usar CloudSaver? - Desarrolladores individuales - Startups en etapa temprana - Equipos pequeños de ingeniería - Agencias que gestionan múltiples cuentas - Cualquier usuario de DigitalOcean preocupado por costos ## Preguntas Frecuentes ### ¿CloudSaver puede borrar mis recursos? No. Solo analiza y recomienda. ### ¿Es realmente gratuito? Sí, sin límites ni suscripciones. ### ¿Cuánto puedo ahorrar? La mayoría de usuarios detecta entre un 10% y un 40% de ahorro. ### ¿Necesito experiencia técnica avanzada? No. Las recomendaciones son claras y accionables. ## Conclusión El desperdicio en la nube no suele ser intencional, pero sí costoso. CloudSaver demuestra que con visibilidad y análisis adecuados es posible reducir gastos sin comprometer seguridad ni rendimiento. Si usas DigitalOcean, auditar tu infraestructura debería ser una práctica habitual. CloudSaver hace que ese proceso sea rápido, seguro y accesible. Cada recurso innecesario eliminado es dinero que puedes reinvertir en crecimiento, producto o estabilidad. [Analiza tu infraestructura gratis con CloudSaver](https://do-cloudsaver.vercel.app/) --- ### Sincronización de Caché en Arquitecturas Híbridas con Laravel - URL: https://www.angelcruz.dev/post/revalidacion-cache-aplicaciones-hibridas - Markdown: https://www.angelcruz.dev/post/revalidacion-cache-aplicaciones-hibridas.md - Categoría: Laravel - Fecha: 2025-12-29 - Excerpt: Sincroniza el caché entre Laravel y Next.js con una estrategia automática basada en eventos, jobs y revalidación selectiva para mejorar SEO y rendimiento. --- title: "Sincronización de Caché en Arquitecturas Híbridas con Laravel" excerpt: "Sincroniza el caché entre Laravel y Next.js con una estrategia automática basada en eventos, jobs y revalidación selectiva para mejorar SEO y rendimiento." date: "2025-12-29T09:00:00.000Z" category: "Laravel" seo_title: "Sincronizar caché entre Laravel y Next.js con jobs y eventos" seo_description: "Implementa revalidación de caché en arquitecturas híbridas Laravel + Next.js usando jobs asíncronos, eventos de modelo y un servicio de rutas centralizado para mantener el SEO técnico sin inconsistencias." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- La sincronización de caché en arquitecturas híbridas es un desafío recurrente cuando se utiliza **Laravel como backend** y **Next.js como frontend**. Este tipo de arquitectura desacoplada ofrece grandes beneficios en rendimiento y escalabilidad, pero introduce un problema crítico: el frontend cachea contenido sin conocer los cambios que ocurren en el backend. Cuando el sistema no cuenta con una estrategia de revalidación, el frontend puede servir información obsoleta, lo que afecta directamente a la experiencia del usuario, al SEO técnico y a la coherencia del contenido. Este artículo describe una solución sólida y escalable para **sincronizar el caché del frontend con los eventos del backend**, aplicable a proyectos reales en producción. ## Problema del Caché en Frontends Modernos Frameworks como Next.js utilizan técnicas avanzadas de optimización como: - Static Site Generation (SSG) - Incremental Static Regeneration (ISR) - Caché a nivel de CDN Estas técnicas permiten tiempos de carga muy bajos, pero generan un punto de fricción cuando el backend modifica datos críticos como posts, categorías o páginas indexables. Desde la perspectiva del frontend, el contenido sigue siendo válido, aunque el backend ya haya cambiado. Esto genera inconsistencias como: - Páginas indexadas con contenido antiguo - URLs eliminadas que siguen siendo accesibles - Sitemaps desactualizados - Problemas de crawl budget y contenido duplicado ## Importancia de la Revalidación de Caché para SEO Técnico Desde el punto de vista del SEO técnico, servir contenido obsoleto tiene consecuencias claras: - Google puede indexar información incorrecta - El sitemap deja de reflejar el estado real del sitio - El feed RSS pierde coherencia - Se generan señales negativas de calidad Por ello, es fundamental que el backend actúe como **fuente única de verdad** y tenga la capacidad de invalidar o revalidar el caché del frontend de forma controlada. ## Arquitectura General de la Solución La estrategia se basa en un flujo unidireccional: 1. Laravel detecta un cambio relevante en los datos 2. Se determina qué rutas del frontend están afectadas 3. El frontend recibe la notificación y revalida su caché Para implementar este flujo sin acoplar ambas capas, se utilizan tres componentes principales: - Jobs en cola para comunicación asíncrona - Un servicio centralizado de resolución de rutas - Eventos de modelos para disparar la revalidación Este enfoque respeta principios de arquitectura limpia y facilita el mantenimiento a largo plazo. ## Job Asíncrono de Revalidación del Frontend El primer componente es un job que se ejecuta en segundo plano y se encarga exclusivamente de notificar al frontend. Desde el punto de vista arquitectónico, este job cumple varias funciones clave: - Desacopla backend y frontend - Evita latencias en la respuesta HTTP - Permite reintentos en caso de fallo - Centraliza la comunicación externa El job envía al frontend un conjunto mínimo de información: rutas afectadas, tipo de recurso y acción realizada. El job puede ser parecido a esto: ```php // app/Jobs/RevalidateNextjsCache.php final class RevalidateNextjsCache implements ShouldQueue { public function __construct( private readonly array $paths = ['/'], private readonly ?string $resourceType = null, private readonly ?int $resourceId = null, private readonly ?string $action = null, ) {} public function handle(): void { $nextjsUrl = config('services.nextjs.url'); $revalidateToken = config('services.nextjs.revalidate_token'); if (! $nextjsUrl || ! $revalidateToken) { Log::warning('Frontend revalidation skipped: missing configuration'); return; } try { $response = Http::timeout(5) ->post("{$nextjsUrl}/api/revalidate", [ 'token' => $revalidateToken, 'paths' => $this->paths, 'resource' => $this->resourceType ? [ 'type' => $this->resourceType, 'id' => $this->resourceId, 'action' => $this->action, 'timestamp' => now()->timestamp, ] : null, ]); if ($response->successful()) { Log::info('Frontend cache revalidated successfully', [ 'paths' => $this->paths, 'response' => $response->json(), ]); } else { Log::error('Frontend revalidation failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); } } catch (Exception $e) { Log::error('Frontend revalidation error', [ 'error' => $e->getMessage(), 'paths' => $this->paths, ]); } } } ``` ## Servicio Centralizado de Resolución de Rutas Uno de los errores más comunes en sistemas de revalidación es dispersar la lógica de rutas por toda la aplicación. Para evitarlo, la solución es tener un servicio dedicado que: - Recibe el tipo de recurso modificado - Evalúa la acción realizada (crear, actualizar, eliminar) - Considera relaciones afectadas (por ejemplo, categorías) - Devuelve un listado preciso de rutas a revalidar Este servicio es clave para garantizar que la revalidación sea **granular**, evitando invalidaciones masivas innecesarias. ```php // app/Services/RevalidationPathService.php final class RevalidationPathService { public static function getPathsForPost(Post $post, string $action, array $categoryPaths = []): array { return match ($action) { 'created', 'updated', 'published' => [ '/', '/post', "/post/{$post->slug}", ], 'deleted' => [ '/', '/post', ], default => ['/'], }; } } ``` ## Uso de Eventos de Modelos en Laravel Laravel proporciona eventos de modelo que permiten reaccionar automáticamente a cambios en la base de datos. Integrar la revalidación en estos eventos aporta varias ventajas: - Automatización total del proceso - Eliminación de llamadas manuales - Menor riesgo de inconsistencias - Mayor trazabilidad de los cambios Los eventos más relevantes para este tipo de sistema suelen ser: - Creación de contenido publicado - Actualización de contenido existente - Eliminación de recursos indexables En tu modelo puedes hacer algo como lo siguiente: ```php // app/Models/YourModel.php protected static function boot(): void { parent::boot(); // Cuando se crea un post publicado self::created(function (Post $post): void { dispatch(new RevalidateNextjsCache( paths: RevalidationPathService::getPathsForPost($post, 'created'), resourceType: 'post', resourceId: $post->id, action: 'created', )); }); // otros eventos } ``` ## Configuración del Sistema La configuración debe realizarse mediante variables de entorno para evitar hardcoding y facilitar despliegues en distintos entornos. Elementos clave de configuración: - URL del frontend - Token de autenticación compartido - Endpoint protegido de revalidación El frontend debe validar el token y ejecutar la revalidación solo si la solicitud es legítima. ## Casos de Uso SEO-Críticos Cubiertos Este sistema cubre escenarios directamente relacionados con SEO técnico: ### Publicación de contenido Revalidación de home, listados, páginas individuales, etc. ### Actualización de contenido Revalidación de páginas afectadas y recursos relacionados. ### Eliminación de contenido Evita URLs huérfanas y páginas inexistentes cacheadas. ### Cambios estructurales Mantiene consistencia en la navegación. ## Beneficios Técnicos de la Implementación - Mantiene coherencia entre backend y frontend - Reduce riesgos de indexación incorrecta - Optimiza el uso del caché - Mejora la calidad del sitemap - Facilita el mantenimiento del SEO técnico ## Buenas Prácticas de Arquitectura Aplicadas - Separación clara de responsabilidades - Uso de colas para tareas no críticas - Configuración externa y segura - Revalidación selectiva y controlada - Diseño orientado a escalabilidad ## Impacto en Rendimiento y SEO Esta estrategia permite combinar: - Rendimiento alto del frontend - Contenido siempre actualizado - Mejor control del crawl budget - Menor riesgo de contenido obsoleto El resultado es una arquitectura preparada para producción y optimizada tanto para usuarios como para motores de búsqueda. ## Conclusión La sincronización de caché entre Laravel y Next.js no es solo un problema técnico, sino un factor clave de SEO y calidad del sitio. Implementar una estrategia de revalidación basada en eventos, jobs y rutas bien definidas permite mantener el frontend rápido sin sacrificar consistencia ni posicionamiento. Esta solución ofrece un equilibrio sólido entre rendimiento, mantenibilidad y SEO técnico. En un próximo artículo hablaré sobre la implementación en una aplicación Next.js. --- ### Rebill para WooCommerce - URL: https://www.angelcruz.dev/post/rebill-woocommerce-gateway-pagos-latam - Markdown: https://www.angelcruz.dev/post/rebill-woocommerce-gateway-pagos-latam.md - Categoría: WordPress - Fecha: 2025-12-28 - Excerpt: Plugin gratuito y de código abierto que integra Rebill en WooCommerce mediante checkout alojado seguro. Sin PCI compliance requerido. Disponible en GitHub. --- title: "Rebill para WooCommerce" excerpt: "Plugin gratuito y de código abierto que integra Rebill en WooCommerce mediante checkout alojado seguro. Sin PCI compliance requerido. Disponible en GitHub." date: "2025-12-28T09:00:00.000Z" category: "WordPress" seo_title: "Rebill para WooCommerce: gateway de pagos LATAM sin PCI DSS" seo_description: "Plugin de código abierto que integra Rebill en WooCommerce con checkout alojado seguro. Compatible con HPOS, bloques Gutenberg, webhooks en tiempo real y soporte para Argentina, Brasil y México." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/3/01KDKRKZVEYCR5DQ0ZT8P9FDHZ.png" --- ## ¿Qué es Rebill WooCommerce? **Rebill WooCommerce** es un plugin de código abierto que integra la pasarela de pago Rebill en tiendas WooCommerce. Está disponible gratuitamente en GitHub bajo licencia GPL-2.0. El plugin redirige a los clientes al **checkout alojado y seguro de Rebill** para completar el pago, lo que elimina por completo la necesidad de certificación PCI DSS en tu tienda. Una vez completado el pago, el cliente vuelve automáticamente a tu tienda y recibe la confirmación del pedido. ## ¿Por qué WooCommerce necesita un gateway optimizado para LATAM? Muchos gateways internacionales no contemplan las particularidades de Latinoamérica, como: - Monedas locales - Métodos de pago regionales - Requisitos fiscales propios de cada país - Experiencia de checkout adaptada a la región **Rebill WooCommerce** resuelve estos problemas ofreciendo una solución pensada para LATAM, con soporte para Argentina, Brasil, Colombia, México, Chile y más. ## Principales características ### Checkout alojado seguro Los clientes son redirigidos a la página de pago segura y alojada de Rebill. No necesitas gestionar datos de tarjeta en tu servidor ni obtener certificación PCI DSS. ### Checkout clásico y basado en bloques Compatible tanto con el checkout tradicional de WooCommerce como con el nuevo **checkout basado en bloques de Gutenberg**. ### Soporte multi-región LATAM Acepta pagos en múltiples países de Latinoamérica. Consulta la [web de Rebill](https://rebill.com) para la lista completa de regiones disponibles. ### Reembolsos desde WooCommerce Desde el panel de administración puedes emitir reembolsos completos, sincronizados automáticamente con Rebill. Actualmente se soportan solo reembolsos totales. ### Webhooks en tiempo real El plugin recibe **webhooks en tiempo real** para actualizar el estado de los pagos y mantener tu tienda sincronizada automáticamente. ## Compatibilidad con productos físicos y digitales El plugin detecta automáticamente si un pedido requiere envío (productos físicos) o no (productos digitales), ajustando el flujo de pago en consecuencia. ## Compatibilidad con HPOS El plugin es totalmente compatible con: - High-Performance Order Storage (HPOS) - Custom Order Tables Esto garantiza mejor rendimiento y compatibilidad futura con WooCommerce. ## Modo de prueba para desarrolladores Incluye un **modo sandbox** que permite probar pagos sin dinero real y validar el flujo completo de checkout antes de pasar a producción. ## Instalación paso a paso 1. Descarga el archivo ZIP desde [GitHub](https://github.com/abr4xas/rebill-for-woocommerce) 2. Ve a **Plugins** > **Añadir nuevo** > **Subir plugin** en tu WordPress 3. Sube el ZIP y activa el plugin 4. Ve a **WooCommerce** > **Ajustes** > **Pagos** > **Rebill** 5. Ingresa tu **Secret Key** de Rebill 6. Configura el webhook (opcional pero recomendado) 7. Realiza un pago de prueba en modo sandbox ## Requisitos técnicos - PHP 8.1 o superior - WordPress 6.5 o superior - WooCommerce 8.0 o superior - Una cuenta activa en Rebill con API credentials ## Descarga gratuita El plugin es completamente gratuito y está disponible en GitHub: [https://github.com/abr4xas/rebill-for-woocommerce](https://github.com/abr4xas/rebill-for-woocommerce) Para reportar problemas o contribuir, abre un issue o pull request en el repositorio. ## Preguntas Frecuentes ### ¿Necesito certificación PCI DSS? No. Los clientes ingresan sus datos en la página alojada de Rebill, por lo que tu tienda no maneja datos sensibles de pago. ### ¿Necesito una cuenta de Rebill? Sí, necesitas una cuenta activa y tu Secret Key desde el panel de Rebill. ### ¿Funciona con checkout basado en bloques? Sí, es 100% compatible con el checkout clásico y el basado en bloques de Gutenberg. ### ¿Puedo usarlo con productos digitales? Sí, funciona con productos físicos y digitales. El plugin ajusta el flujo automáticamente. ### ¿Puedo procesar reembolsos? Sí, reembolsos completos directamente desde WooCommerce. Los reembolsos parciales no están disponibles actualmente. ### ¿Cómo recibo actualizaciones? El repositorio es público en GitHub. Puedes hacer watch/star para recibir notificaciones de nuevas versiones. ### ¿Dónde reporto problemas? En los [Issues de GitHub](https://github.com/abr4xas/rebill-for-woocommerce/issues). ## Conclusión **Rebill WooCommerce** es una solución gratuita y de código abierto para integrar Rebill en tiendas WooCommerce. Sin costos, sin suscripciones, sin PCI compliance. Ideal para comerciantes latinoamericanos que buscan una pasarela de pago confiable y fácil de configurar. --- ### Por qué las pruebas técnicas automatizadas no reflejan realmente el potencial del desarrollador - URL: https://www.angelcruz.dev/post/las-pruebas-tecnicas-no-miden-el-talento-real - Markdown: https://www.angelcruz.dev/post/las-pruebas-tecnicas-no-miden-el-talento-real.md - Categoría: Opinión - Fecha: 2025-10-29 - Excerpt: Las pruebas técnicas automatizadas miden velocidad y memorización, no el potencial real del desarrollador. Un análisis de sus sesgos, limitaciones y por qué el talento técnico se evalúa mejor con entrevistas contextuales. --- title: "Por qué las pruebas técnicas automatizadas no reflejan realmente el potencial del desarrollador" excerpt: "Las pruebas técnicas automatizadas miden velocidad y memorización, no el potencial real del desarrollador. Un análisis de sus sesgos, limitaciones y por qué el talento técnico se evalúa mejor con entrevistas contextuales." date: "2025-10-29T00:15:59.000Z" category: "Opinión" seo_title: "Las pruebas técnicas automatizadas no miden el talento real" seo_description: "Las pruebas técnicas miden velocidad y memorización, no capacidad real de ingeniería. El talento se evalúa mejor con pair programming, revisión de proyectos y entrevistas contextuales." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Las pruebas técnicas en programación se han convertido en una herramienta estándar dentro de los procesos de selección de talento tecnológico. Desde plataformas que evalúan algoritmos hasta ejercicios automatizados que miden la eficiencia del código, casi toda empresa tecnológica las utiliza. Sin embargo, su creciente popularidad ha traído consigo una pregunta incómoda: ¿realmente miden el potencial de quien programa? > La respuesta, aunque muchos prefieren evitarla, es que no siempre. ## Un filtro que prioriza la técnica sobre la realidad El problema principal es que estas pruebas suelen medir habilidades aisladas y no competencias reales de trabajo. Un desarrollador puede no destacar en un desafío de algoritmos complejos, pero tener una enorme capacidad para diseñar soluciones escalables, comunicarse con su equipo y entregar software confiable en contextos reales. Las pruebas técnicas automatizadas, por su naturaleza, ignoran estos matices. Además, en entornos de desarrollo modernos, la colaboración, la lectura de código existente y la toma de decisiones arquitectónicas pesan mucho más que resolver un problema matemático en un entorno artificial. ## El contexto importa (y las pruebas lo eliminan) Cuando un desarrollador trabaja en un proyecto real, dispone de contexto: conoce el propósito del producto, el público, los plazos y los recursos disponibles. En cambio, las pruebas técnicas en programación eliminan ese contexto, lo que convierte la tarea en un ejercicio de memoria y velocidad, no de ingeniería de software. Esto afecta especialmente a desarrolladores con experiencia en sistemas grandes o con metodologías ágiles, donde el valor no está en escribir código rápido, sino en entender el problema y diseñar la mejor solución posible. ## La presión y el sesgo del formato Otro factor poco discutido es el sesgo psicológico. Muchos desarrolladores experimentan estrés ante las pruebas cronometradas o las entrevistas en vivo, donde se les pide escribir código sin acceso a documentación o sin su entorno de trabajo habitual. Esa presión no refleja cómo se desempeñarían en su día a día, cuando pueden analizar, investigar y validar decisiones antes de implementarlas. Por otro lado, las pruebas técnicas automatizadas suelen beneficiar a quienes entrenan específicamente para superarlas, no necesariamente a quienes mejor aplican la ingeniería de software en la práctica. Esto genera un sesgo hacia candidatos que dominan el formato más que la profesión. ## Evaluar talento requiere mirar más allá del código Un buen proceso de selección no debería basarse únicamente en una métrica automatizada. Algunos equipos complementan las pruebas técnicas con evaluaciones de pensamiento crítico, revisión de proyectos anteriores, pair programming guiado y entrevistas técnicas contextuales. Estas alternativas ofrecen una visión más completa del candidato: cómo se comunica, cómo prioriza tareas, cómo aborda la incertidumbre o cómo justifica sus decisiones técnicas. > Los reclutadores que tienen esto en cuenta suelen identificar candidatos que no solo “saben programar”, sino que saben construir software útil en contextos reales. ## El valor del razonamiento sobre la memorización El verdadero potencial de un desarrollador no está en recordar cada detalle del lenguaje, sino en su capacidad de razonamiento, aprendizaje y adaptación. La industria cambia constantemente, y quien hoy domina un framework puede mañana estar aprendiendo otro. Una prueba automatizada que evalúa funciones específicas de sintaxis no mide eso. Por el contrario, una entrevista técnica bien estructurada, basada en problemas abiertos y diálogo, puede revelar cómo piensa el candidato, cómo colabora y cómo aprende frente a un desafío nuevo. ## Hacia una evaluación más humana y efectiva No se trata de eliminar las pruebas técnicas en programación, sino de darles el lugar que merecen: una herramienta complementaria, no definitiva. Usadas con criterio, pueden servir para validar conocimientos básicos o confirmar que alguien domina los fundamentos de un lenguaje. Pero pretender que reflejen el potencial completo de un desarrollador es una simplificación peligrosa. El talento técnico no puede reducirse a un puntaje automatizado. Se evalúa mejor conversando, colaborando y observando cómo una persona piensa, comunica y resuelve problemas reales. --- ### Cache UI Laravel: administra claves de caché en Redis, File y Database sin borrar todo - URL: https://www.angelcruz.dev/post/cache-ui-laravel-herramienta-para-gestionar-cache - Markdown: https://www.angelcruz.dev/post/cache-ui-laravel-herramienta-para-gestionar-cache.md - Categoría: Laravel - Fecha: 2025-10-07 - Excerpt: Cache UI Laravel es un paquete open source para administrar claves de caché en Laravel de forma selectiva. Lista, busca, previsualiza y elimina claves específicas en Redis, File y Database sin borrar todo el caché. --- title: "Cache UI Laravel: administra claves de caché en Redis, File y Database sin borrar todo" excerpt: "Cache UI Laravel es un paquete open source para administrar claves de caché en Laravel de forma selectiva. Lista, busca, previsualiza y elimina claves específicas en Redis, File y Database sin borrar todo el caché." date: "2025-10-07T00:22:18.000Z" category: "Laravel" seo_title: "Cache UI Laravel: gestiona claves de caché desde la CLI" seo_description: "Cache UI Laravel permite listar, buscar, previsualizar y eliminar claves de caché de forma selectiva en Redis, File y Database con un solo comando Artisan." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Cuando trabajamos con **Laravel**, el uso de caché es fundamental para mejorar el rendimiento de nuestras aplicaciones. Sin embargo, gestionar y depurar claves específicas puede convertirse en un dolor de cabeza: ¿qué pasa si solo queremos eliminar una clave puntual sin vaciar todo el caché? Para resolver este problema nace **[Cache UI Laravel](https://github.com/abr4xas/cache-ui-laravel)**, un paquete que desarrollé con el objetivo de simplificar la administración de claves de caché en proyectos Laravel. En este artículo te contaré qué hace, cómo instalarlo y cómo puede ayudarte en tu día a día como desarrollador. ![Cache UI Laravel: administra claves de caché en Redis, File y Database sin borrar todo](https://angelcruz.dev/storage/article/hb5WbteVRbxGBclaz77wxjMsJSneXXmAaybb5Nh7.png) ## ¿Qué es Cache UI Laravel? **Cache UI Laravel** es un paquete que te permite: - Listar todas las claves de caché. - Buscar de forma interactiva. - Previsualizar el valor de cada clave. - Eliminar claves específicas sin borrar todo el caché. - Usar distintos drivers de caché como **Redis**, **File** y **Database**. Todo esto desde una **interfaz de línea de comandos interactiva**, sin necesidad de crear scripts adicionales ni tocar directamente tu almacenamiento de caché. ## Instalación La instalación es tan simple como ejecutar: ```bash composer require abr4xas/cache-ui-laravel ``` Opcionalmente, puedes publicar el archivo de configuración: ```bash php artisan vendor:publish --tag="cache-ui-laravel-config" ``` En el archivo `config/cache-ui-laravel.php` (o desde tu `.env`), podrás personalizar: - **`CACHE_UI_DEFAULT_STORE`**: store por defecto a usar (ej: `redis`). - **`CACHE_UI_PREVIEW_LIMIT`**: límite de caracteres en la vista previa del valor. - **`CACHE_UI_SEARCH_SCROLL`**: número de items visibles al buscar. ## Uso básico El comando principal es: ```bash php artisan cache:list ``` Con esto, se mostrará un listado interactivo de todas las claves disponibles en tu caché. Si trabajas con múltiples stores, puedes especificar cuál usar: ```bash php artisan cache:list --store=redis ``` ## Principales características - **Búsqueda interactiva** de claves. - **Listado completo** de las claves almacenadas. - **Eliminación selectiva**, sin afectar al resto del caché. - **Soporte para múltiples drivers**: Redis, File, Database. ## Ejemplo en acción ```bash $ php artisan cache:list 📦 Cache driver: redis ✅ Found 23 cache keys 🔍 Search and select a cache key to delete > user_1_profile 📝 Key: user_1_profile Are you sure you want to delete this cache key? › No / Yes 🗑️ The key 'user_1_profile' has been successfully deleted ``` De esta forma, puedes administrar tu caché de manera precisa y sin riesgos de borrar datos importantes por accidente. ## Conclusión **Cache UI Laravel** es un paquete pensado para hacer más ágil y segura la gestión del caché en tus aplicaciones Laravel. Si trabajas con **Redis** o **Database caching** y sueles necesitar depurar claves puntuales, este paquete puede ahorrarte tiempo y dolores de cabeza. Puedes instalarlo ya mismo desde [Packagist](https://packagist.org/packages/abr4xas/cache-ui-laravel) o ver el código en [GitHub](https://github.com/abr4xas/cache-ui-laravel). --- ### IA en WhatsApp 2025: lista completa y cómo usarlas - URL: https://www.angelcruz.dev/post/inteligencias-artificiales-en-whatsapp - Markdown: https://www.angelcruz.dev/post/inteligencias-artificiales-en-whatsapp.md - Categoría: Inteligencia Artificial - Fecha: 2025-09-28 - Excerpt: Lista completa y verificada de inteligencias artificiales accesibles por WhatsApp en 2025: ChatGPT, Copilot, Perplexity, Grok y más. Incluye números de contacto, enlaces wa.me y cómo usar cada una. --- title: "IA en WhatsApp 2025: lista completa y cómo usarlas" excerpt: "Lista completa y verificada de inteligencias artificiales accesibles por WhatsApp en 2025: ChatGPT, Copilot, Perplexity, Grok y más. Incluye números de contacto, enlaces wa.me y cómo usar cada una." date: "2025-09-28T01:19:39.000Z" category: "Inteligencia Artificial" seo_title: "IAs en WhatsApp 2025: lista completa con números y enlaces" seo_description: "Lista verificada de 16 inteligencias artificiales disponibles en WhatsApp en 2025: ChatGPT, Copilot, Perplexity, Grok y más. Números de contacto y enlaces wa.me directos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/content/1/01K66Z5GEN4N60729ZMNES05ZS.png" --- ![IA en WhatsApp 2025: lista completa y cómo usarlas](https://angelcruz.dev/storage/article/CzQXQpfuEEuBnACTc5XxUbdH9SqUTUejmSdVUeps.png) ## Por qué conectar una IA con WhatsApp puede cambiar tu forma de interactuar WhatsApp es una de las plataformas de mensajería más utilizadas del mundo. Integrar una inteligencia artificial (IA) directamente en esta aplicación permite acceder a respuestas automatizadas, asistencia personalizada y herramientas de productividad sin necesidad de instalar nuevas apps, crear cuentas adicionales ni aprender interfaces complejas. Basta con guardar un número o hacer clic en un enlace wa.me y comenzar a chatear. - No requiere registro ni configuración avanzada. - Funciona en móvil, web y escritorio. - Acepta preguntas abiertas y responde en lenguaje cotidiano. - Disponible en cualquier país, con soporte multilingüe. - Cubre temas variados: desde recetas hasta dudas técnicas o filosóficas. ## Tipos de conexión entre IA y WhatsApp 1. **Acceso directo por número o enlace wa.me** - Guarda un número en tu agenda o abre un enlace como `https://wa.me/18002428478` y comienzas a chatear. 2. **Integración nativa en la aplicación** - Algunas IA, como **Meta AI**, están integradas directamente en WhatsApp en ciertos países. Se activan escribiendo `@MetaAI` en un chat, sin necesidad de número. ## Diferencia entre un bot temático y una IA conversacional - **Bots temáticos:** responden dentro de un contexto específico (ventas, educación, viajes), no entienden preguntas fuera de su dominio y no mantienen el hilo de la conversación. - **IA conversacionales:** responden sobre cualquier tema, mantienen contexto y adaptan el lenguaje. Algunas tienen límites diarios, pero no se bloquean ante temas inesperados. Una IA conversacional en WhatsApp puede ayudarte a redactar un correo, explicarte qué es la mecánica cuántica, darte una receta o responder una pregunta filosófica. Un bot temático, en cambio, solo sirve dentro de su función predefinida. ## Inteligencias artificiales disponibles en WhatsApp (verificadas al 27 de septiembre de 2025) Todas las siguientes IA han sido verificadas como asistentes conversacionales accesibles por WhatsApp. Se indica idioma, país de origen y números activos. - **Nota práctica:** guarda el número en tus contactos o haz clic en el enlace wa.me proporcionado para directamente chatear. ### IA con funciones completas #### ChatGPT - **Desarrolladora:** OpenAI - **Idiomas:** español, inglés y principales - **País:** Estados Unidos (uso global) - **Números:** +1 800 242 8478 (EE.UU.) - **Enlace:** https://wa.me/18002428478 - **Característica única:** respuestas detalladas multilingües #### Copilot - **Desarrolladora:** Microsoft - **Idiomas:** español e inglés - **País:** Estados Unidos (uso global) - **Números:** +1 877 224 1042 (EE.UU.) - **Enlace:** https://wa.me/18772241042 - **Característica única:** integración con Microsoft 365 y productividad #### Perplexity AI - **Tipo:** asistente de búsqueda con fuentes en tiempo real - **Idiomas:** inglés (soporte básico en español) - **País:** Estados Unidos (uso global) - **Números:** +1 833 436 3285 (EE.UU.), +1 833 436 3288 (EE.UU.) - **Enlace:** https://wa.me/18334363285 - **Característica única:** verificación de noticias y enlaces confiables #### Grok - **Desarrolladora:** xAI - **Idiomas:** inglés - **País:** Estados Unidos (xAI, Texas; uso global) - **Números:** +65 8205 9883 (Singapur; ruteo global vía Kiitos) - **Enlace:** https://wa.me/6582059883 - **Característica única:** conversación libre con humor #### MobileGPT - **Tipo:** multimodal (texto, voz, imágenes, PDFs) - **Idiomas:** español e inglés - **País:** Estados Unidos (uso global) - **Números:** +1 415 523 8888 (EE.UU.) - **Enlace:** https://wa.me/14155238888 - **Característica única:** Talk2PDF y generación de documentos en Word desde WhatsApp #### Jinni - **Idiomas:** más de 100 (incluye español) - **País:** Reino Unido (uso global) - **Números:** +44 7890 000000 (Reino Unido) - **Enlace:** https://wa.me/447890000000 - **Característica única:** conversación abierta con soporte de voz #### ChatChit AI - **Idiomas:** multilingüe - **País:** Reino Unido (uso global) - **Números:** +44 7893 980000 (Reino Unido) - **Enlace:** https://wa.me/447893980000 - **Característica única:** generación de imágenes y stickers en WhatsApp #### Hey Pat - **Idiomas:** multilingüe - **País:** Reino Unido (uso global) - **Números:** +44 7700 900982 (Reino Unido) - **Enlace:** https://wa.me/447700900982 - **Característica única:** asistente versátil para tareas cotidianas #### Shmooz AI - **Idiomas:** inglés y español - **País:** Estados Unidos (uso global) - **Números:** +1 201 416 6644 (EE.UU.) - **Enlace:** https://wa.me/12014166644 - **Característica única:** generación de imágenes y respuestas rápidas #### Carina IA - **Origen:** Galicia (España), creada por Daniel Dacuña - **Idiomas:** español (principal) e inglés básico - **País:** España (uso extendido en América Latina) - **Números:** +34 611 22 85 54 (España) - **Enlace:** https://wa.me/34611228554 - **Característica única:** transcripción de audios y consejos personalizados ### IA con funciones básicas #### LuzIA - **Tipo:** transcripción y conversación básica - **Idiomas:** español y portugués - **País:** España (uso extendido en América Latina) - **Números:** +34 613 288 116 (España), +55 11 97255 3036 (Brasil) - **Enlace:** https://wa.me/34613288116 - **Característica única:** transcripción de audios de WhatsApp #### Cami IA - **Tipo:** voz y texto - **Idiomas:** español e inglés - **País:** Estados Unidos (uso global) - **Números:** +1 917 694 2789 (EE.UU.) - **Enlace:** https://wa.me/19176942789 - **Característica única:** conversación por voz integrada #### Zapia AI - **Tipo:** regional, con soporte de audios y respuestas generales - **Idiomas:** español - **País:** Uruguay (uso en América Latina) - **Números:** +598 94 101 100 (Uruguay), +54 11 5199 0501 (Argentina), +52 744 602 0040 (México), +57 4609 0016 (Colombia), +51 989 410 100 (Perú) - **Enlace:** https://wa.me/541151990501 - **Característica única:** IA latinoamericana con enfoque en audios #### Ask Robot One - **Tipo:** básico, en español e inglés - **País:** México (uso regional) - **Números:** +52 1 33 1574 1776 (México) - **Enlace:** https://wa.me/5213315741776 - **Característica única:** chat simple y accesible #### Yatter AI - **Tipo:** asistente indio con voz, análisis de imágenes/PDFs y recordatorios - **Idiomas:** inglés, hindi y español básico - **País:** India (uso regional y global) - **Números:** +91 97186 65000 (India) - **Enlace:** https://wa.me/919718665000 - **Característica única:** productividad con análisis de PDFs #### Puch AI - **Tipo:** primer agente WhatsApp de Bharat (India) - **Idiomas:** multilingüe (hindi, inglés, español, etc.), con voz y fact‑checking - **País:** India (uso regional y global) - **Números:** +91 99988 81729 (India), +91 90909 09090 (India, premium para voz) - **Enlace:** https://wa.me/919998881729 - **Característica única:** optimizado para idiomas locales y verificación de datos ## Limitaciones de uso - **ChatGPT:** en algunos países ofrece solo 10 mensajes gratuitos al día. - **Perplexity:** centrado en inglés; el soporte en español es básico. - **Grok:** puede tener restricciones de acceso según región. - **Funciones premium:** algunas IA ofrecen planes de pago para ampliar límites. ## Cómo usar estas IAs en WhatsApp 1. Guarda el número en tus contactos. 2. Abre WhatsApp y busca el contacto. 3. Envía un mensaje inicial como “Hola” o “Hi” para activar la IA. 4. Prueba preguntas simples: “¿Qué es la IA?” o “Dame una receta”. 5. O haz clic directamente en el enlace wa.me proporcionado para comenzar a chatear sin necesidad de guardarlo. ## Fuentes y recursos (verificados al 27/09/2025) - **ChatGPT (OpenAI):** https://www.infobae.com/tecno/2025/08/26/como-usar-chatgpt-en-whatsapp-guia-facil-para-chatear-con-la-ia-en-tu-celular/ - **Copilot (Microsoft):** https://sleekflow.io/blog/whatsapp-copilot - **Perplexity AI:** https://wwwhatsnew.com/2025/05/03/como-instalar-perplexity-en-whatsapp/ - **Grok (xAI):** https://www.problogbooster.com/2025/04/get-grok-in-whatsapp-use-free-ai-assistance.html - **MobileGPT:** https://mobile-gpt.io/chatgpt-blog/mastering-the-basics-setting-up-mobilegpt-on-your-whatsapp-766895511482 - **Jinni:** https://theresanaiforthat.com/ai/jinni/ - **ChatChit AI:** https://aitoolsexplorer.com/ai-tools/chatchit-whatsapp-ai-chatbot/ - **Hey Pat:** https://heypat.ai/ - **Shmooz AI:** https://shmooz.ai/ - **Carina IA:** https://www.xataka.com/basics/carina-ia-para-whatsapp-que-como-usar-este-asistente-espanol-inteligencia-artificial-gratis - **LuzIA:** https://www.todoandroid.es/como-instalar-luzia-en-whatsapp/ - **Cami IA:** https://depor.com/depor-play/tecnologia/whatsapp-numero-de-camiai-inteligencia-artificial-agregar-nnda-nnni-noticia/ - **Zapia AI:** https://zapia.com/?lang=es - **Ask Robot One:** https://askrobot.one/more - **Yatter AI:** https://yatter.in/ - **Puch AI:** https://aigyani.com/puch-ai/ - **Meta AI (integrada en WhatsApp):** https://www.xatakandroid.com/tutoriales/meta-ai-whatsapp-que-como-usar-ella-como-activarla **Nota:** Enlaces verificados al 27/09/2025; los números de WhatsApp pueden variar según región o actualizaciones de cada servicio. ## Conclusión En 2025, WhatsApp se consolida como un canal clave para acceder a inteligencias artificiales conversacionales. Desde gigantes globales como **ChatGPT** y **Copilot**, hasta proyectos regionales como **Carina** en España, **Zapia** en Uruguay, o **Yatter** y **Puch** en India, las opciones son diversas y útiles. La diferencia entre un bot temático y una IA abierta es fundamental: mientras los primeros solo sirven para tareas específicas, las segundas permiten conversaciones libres y útiles en múltiples idiomas. **Meta AI** merece mención aparte: integrada directamente en WhatsApp, no requiere número ni enlace, y representa la apuesta de Meta por llevar la IA al centro de la mensajería global. --- ### Context7: Documentación siempre actualizada para LLMs y asistentes de código - URL: https://www.angelcruz.dev/post/context7-documentacion-actualizada-asistentes-codigo-ia - Markdown: https://www.angelcruz.dev/post/context7-documentacion-actualizada-asistentes-codigo-ia.md - Categoría: Inteligencia Artificial - Fecha: 2025-09-09 - Excerpt: Context7 brinda documentación oficial y actualizada a asistentes de código IA, evitando errores por ejemplos obsoletos y APIs desactualizadas. --- title: "Context7: Documentación siempre actualizada para LLMs y asistentes de código" excerpt: "Context7 brinda documentación oficial y actualizada a asistentes de código IA, evitando errores por ejemplos obsoletos y APIs desactualizadas." date: "2025-09-09T00:35:21.000Z" lastModified: "2026-05-30T00:00:00.000Z" category: "Inteligencia Artificial" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/mcp-opengraph-image.png" seo_title: "¿Qué es Context7? MCP de documentación al día para IA (2026)" seo_description: "Tu asistente de IA inventa APIs que ya no existen. Context7 le inyecta documentación oficial de +1000 librerías en Cursor, Claude y VS Code. Gratis." --- ## ¿Qué es Context7? Context7 es un servidor MCP (Model Context Protocol) desarrollado por Upstash que proporciona documentación actualizada de **más de 1000 librerías** directamente a tus asistentes de código IA como Claude, Cursor, VSCode y Windsurf. Elimina el problema de código obsoleto y APIs deprecadas que ya no existen, inyectando documentación **version-específica** y **actualizada** proveniente directamente de la fuente oficial. **Cómo funciona:** 1. Detecta automáticamente la librería mencionada en tu prompt 2. Descarga fragmentos de documentación y ejemplos de código reales desde la fuente oficial 3. Procesa, limpia y jerarquiza la información por relevancia 4. Entrega esa documentación directamente dentro del contexto del modelo **Resultado:** Código que compila en el primer intento, sin APIs deprecadas ni ejemplos obsoletos. > **¿Buscas alternativas?** Lee nuestra [comparativa Context7 vs DeepWiki](/post/context7-vs-deepwiki-comparativa) para entender cuándo usar cada uno. ## ¿Qué es MCP (Model Context Protocol)? Context7 funciona sobre el **Model Context Protocol (MCP)**, un estándar abierto introducido por [Anthropic en noviembre de 2024](https://www.anthropic.com/news/model-context-protocol) para estandarizar cómo los sistemas de IA se integran con herramientas y fuentes de datos externas. MCP es descrito como el **"puerto USB-C para aplicaciones de IA"**: así como USB-C estandarizó las conexiones físicas, MCP estandariza las conexiones entre LLMs y servicios externos. ### Adopción y estado actual (2026) - **Diciembre 2025**: Anthropic [donó MCP a la Agentic AI Foundation](https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation) (AAIF), un fondo bajo la Linux Foundation - **Co-fundadores**: Anthropic, Block (anteriormente Square) y OpenAI - **Adoptado por**: OpenAI, Google DeepMind, y otras empresas importantes - **Ecosistema Claude**: Más de 75 conectores MCP disponibles ([directorio oficial](https://modelcontextprotocol.io/)) ### Novedad 2026: MCP Apps En enero de 2026, Anthropic expandió MCP para permitir que [aplicaciones presenten interfaces dentro de Claude](https://www.theregister.com/2026/01/26/claude_mcp_apps_arrives/), mostrando gráficos, formularios y dashboards directamente en la ventana de chat. ## Beneficios principales - **Siempre actualizado**: Evita el uso de ejemplos obsoletos o APIs inexistentes - **Conciso y relevante**: Filtra el contenido útil, sin saturaciones - **Gratis para uso personal/educativo**: Desarrollado como proyecto de Upstash - **Compatible con múltiples editores**: Cursor, Windsurf, VS Code, Claude, Copilot - **Más de 1000 librerías soportadas**: JavaScript, TypeScript, Python, Go, Rust y más ## Librerías soportadas Context7 soporta las librerías más populares en múltiples lenguajes: - **Frontend:** React, Vue, Next.js, Nuxt, Svelte, Angular, Solid - **Backend:** Laravel, Django, Rails, Express, FastAPI, Nest.js - **Databases:** Prisma, Mongoose, Eloquent, TypeORM, Drizzle - **Build Tools:** Vite, Webpack, Turbopack, esbuild, Rollup - **Styling:** Tailwind CSS, Shadcn UI, Radix UI, MUI - **Testing:** Vitest, Jest, Playwright, Cypress - **Y más de 1000 librerías adicionales** Ver la [lista completa de librerías soportadas](https://github.com/upstash/context7) en el repositorio oficial. ## Instalación rápida ### Opción A (recomendada): setup automático con la CLI La forma más rápida y actual de configurar Context7 es con su CLI oficial. Un solo comando resuelve la autenticación por OAuth, genera tu API key e instala el skill para tu agente (Cursor, Claude Code, OpenCode y otros): ```bash npx ctx7 setup ``` Context7 ofrece dos modos: **CLI + Skills** (el que instala el comando anterior) o **MCP nativo**. El paquete funciona con más de 30 clientes. ### Opción B: configuración manual vía MCP Si prefieres conectarlo como servidor MCP a mano, edita la configuración de tu editor. **Para Claude Desktop:** - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` Añade Context7 a la sección `mcpServers` (el `--api-key` es opcional, pero da límites de uso más altos): ```json { "mcpServers": { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp", "--api-key", "TU_API_KEY"] } } } ``` Consigue una **API key gratuita** en [context7.com/dashboard](https://context7.com/dashboard) para subir los límites de rate. Para Cursor, Windsurf o VS Code, revisa la [guía completa de instalación](https://github.com/upstash/context7?tab=readme-ov-file#%EF%B8%8F-installation) en el repositorio oficial. ### Paso 3: Usar Context7 **Sintaxis simple** - En Cursor o Claude, solo menciona: ``` use context7 ``` **Sintaxis específica** - Para librerías concretas, usa slash syntax: ``` /context7 react-hook-form ``` Context7 descargará automáticamente la documentación actualizada y version-específica de esa librería. **Ejemplo completo:** ``` Prompt: "Crea un formulario en React con react-hook-form y Zod usando /context7" → Context7 descarga docs actualizadas de react-hook-form y Zod → Claude/Cursor responde con código actualizado y funcional ``` Fuente: [GitHub Context7](https://github.com/upstash/context7), [Upstash Blog](https://upstash.com/blog/context7-mcp) ## Testimonios reales - En Hacker News, un usuario comenta: > "I've had good success with the Context7 model context protocol tool, which allows code agents, like GitHub Copilot, to look up the latest relevant version of library documentation including code snippets" ([news.ycombinator.com](https://news.ycombinator.com/item?id=44071551)). - En Medium, Matteo Ferruccio Andreoni relata cómo Context7 cambió su flujo de trabajo: > "Instead of me shoveling documentation into the model, the service > automatically pulls the right snippet, filtered by library and > version, and feeds it to the assistant... the code it proposes > usually compiles on the first try." > ([medium.com](https://medium.com/%40matteo28/how-context7-mcp-by-upstash-transformed-my-vscode-and-copilot-workflow-1658a7826ec4)). ## Casos de uso 1. **Uso manual**: Copia y pega fragmentos desde Context7 directamente a tu editor o interfaz de Chat de Cursor, Claude, etc. 2. **Integración automática vía MCP**: Context7 se acopla al flujo del editor y enriquece las respuestas sin pasos manuales. 3. **Con asistentes locales**: Context7 funciona con editores tradicionales (Cursor, VSCode), pero también con asistentes IA locales como [OpenClaw](/post/clawdbot-asistente-ia-personal-open-source), que ejecuta tareas autónomas en tu dispositivo. Esto permite tener documentación actualizada incluso en workflows completamente self-hosted. 4. **Para autores de librerías**: Se puede añadir tu proyecto en context7.com o enviar un PR en GitHub para que se genere automáticamente el archivo `llms.txt` optimizado para LLMs. ## Funcionamiento interno Context7: - Extrae fragmentos de código y ejemplos desde la documentación oficial. - Agrega explicaciones cortas usando LLMs. - Vectoriza y ordena por relevancia mediante un algoritmo propio. - Almacena en cache (en Redis) para respuesta rápida ([upstash.com](https://upstash.com/blog/context7-llmtxt-cursor)). ## En resumen 1. **¿Qué es?** Servidor MCP que inyecta documentación oficial actualizada 2. **Ventajas** Exactitud, relevancia, ahorro de tiempo, gratis (uso personal) 3. **Compatibilidad** Cursor, Windsurf, VS Code + Copilot, Claude, otros. 4. **Modo de uso** Manual (copiar/pegar) o automático (MCP integrado). 5. **Testimonios** Usuarios reportan mejor compilación en primera sugerencia. 6. **Tecnología interna** Parsing, enriquecimiento, vectorización, ranking, cache. ## Recursos oficiales - **Repositorio GitHub**: [upstash/context7](https://github.com/upstash/context7) - **NPM Package**: [@upstash/context7-mcp](https://www.npmjs.com/package/@upstash/context7-mcp) - **Blog Upstash**: [Introducing Context7](https://upstash.com/blog/context7-mcp) - **MCP Official**: [Model Context Protocol](https://modelcontextprotocol.io/) - **Directorio MCP**: [LobeHub](https://lobehub.com/mcp/upstash-context7), [Smithery](https://smithery.ai/server/@upstash/context7-mcp) ## Enlaces de interes - GitHub y sitio oficial de Context7 --- explicación general y arquitectura ([github.com](https://github.com/upstash/context7), [upstash.com](https://upstash.com/blog/context7-llmtxt-cursor), [context7.com](https://context7.com/)). - Blog de Upstash (marzo 2025): introducción, beneficios, "llms.txt" ([upstash.com](https://upstash.com/blog/context7-llmtxt-cursor)). - Guía de instalación y uso detallado en Apidog (julio 2025) ([apidog.com](https://apidog.com/blog/context7-mcp-server/)). - Artículo en Medium (junio 2025): experiencia práctica de integración ([medium.com](https://medium.com/%40matteo28/how-context7-mcp-by-upstash-transformed-my-vscode-and-copilot-workflow-1658a7826ec4)). - Comentario en Hacker News: experiencia de usuario ([news.ycombinator.com](https://news.ycombinator.com/item?id=44071551)). --- ### 15 Mejores Herramientas Gratuitas para Desarrolladores en 2025 que Aceleran tu Flujo de Trabajo - URL: https://www.angelcruz.dev/post/herramientas-gratis-para-programadores - Markdown: https://www.angelcruz.dev/post/herramientas-gratis-para-programadores.md - Categoría: Herramientas - Fecha: 2025-09-01 - Excerpt: Herramientas gratuitas para desarrolladores: descubre cómo elegir, integrar y aprovechar las mejores opciones de software, diseño, colaboración y optimización para acelerar tu flujo de trabajo en 2025. --- title: "15 Mejores Herramientas Gratuitas para Desarrolladores en 2025 que Aceleran tu Flujo de Trabajo" excerpt: "Herramientas gratuitas para desarrolladores: descubre cómo elegir, integrar y aprovechar las mejores opciones de software, diseño, colaboración y optimización para acelerar tu flujo de trabajo en 2025." date: "2025-09-01T16:23:07.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "Herramientas" seo_title: "15 herramientas gratuitas para desarrolladores en 2025" seo_description: "Guía de herramientas gratuitas para programadores en 2025: VS Code, GitHub, Figma, Postman, Tailwind CSS y más. Stack completo para gestión, diseño, rendimiento y automatización." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Hay un buen número de herramientas gratuitas disponibles para desarrolladores que cubren las distintas fases del trabajo: gestión de proyectos, edición de código, diseño, automatización y rendimiento. Esta guía repasa las opciones más usadas en 2025, cómo encajan en un stack típico y qué ofrece cada una. ## Por qué usar herramientas gratuitas en tu stack de desarrollo El auge del **software open source** y los modelos freemium ha democratizado el acceso a soluciones potentes. Tanto freelancers como grandes equipos pueden crear proyectos competitivos sin grandes inversiones iniciales. **Ventajas principales:** - Reducción de costes sin sacrificar calidad. - Fácil integración con servicios populares. - Colaboración remota más ágil. - Aprendizaje continuo gracias a comunidades activas. Eso sí: no todas las herramientas gratuitas ofrecen el mismo soporte o escalabilidad. La clave está en **elegir inteligentemente** las que mejor encajen en tu flujo de trabajo. ## Gestión de proyectos y colaboración Organizar bien las tareas es la base de todo proyecto exitoso. ### ClickUp Gestión ágil de proyectos, paneles personalizados y automatización de flujos de trabajo. Su plan gratuito cubre tareas, sprints e incluso documentación. ### GitHub Más que control de versiones: incluye issues, documentación en Markdown, CI/CD integrado y una comunidad global inmensa. ### Trello, Notion y Jira (free plan) - Trello: simplicidad visual con tableros Kanban. - Notion: excelente para wikis y documentación centralizada. - Jira: plan gratuito con funciones para gestión ágil de equipos técnicos. ## Editores de código y entornos de desarrollo El **IDE o editor de código** es el corazón de tu productividad. ### Visual Studio Code (VS Code) El más popular por velocidad, extensiones y soporte para múltiples stacks. Perfecto para integrar linters, Docker y Git. ### DevKinsta Ideal para desarrolladores WordPress: entornos locales completos en un clic, gratis y multiplataforma. ### StackBlitz y CodeSandbox Editores online que permiten prototipar y compartir proyectos en segundos. ## Frameworks y librerías front-end La velocidad de desarrollo depende de contar con herramientas listas para interfaces modernas. - Bootstrap: componentes prediseñados, responsive y gran comunidad. - Tailwind CSS: enfoque “utility-first” para máxima personalización. - Angular: framework completo para apps web robustas. - Animate.css & Swiper.js: animaciones y sliders fáciles de integrar. ## Herramientas de diseño y prototipado Un buen diseño es clave para cualquier proyecto digital. - Figma: estándar en diseño colaborativo de interfaces. - Canva y Photopea: gráficos rápidos o edición avanzada de imágenes. - Google Fonts & Flaticon: tipografías e iconos gratuitos para cualquier proyecto. ## Optimización y rendimiento El **rendimiento web** es vital para SEO y experiencia de usuario. - Google PageSpeed Insights: auditoría de velocidad y sugerencias. - GTmetrix: métricas detalladas de carga y optimización. - Cloudflare: CDN gratuita con seguridad y aceleración de contenido. ## Control de versiones y automatización - Git + GitHub: control de versiones y trabajo colaborativo. - GitHub Actions: automatiza pruebas, builds y despliegues directamente en tu repo. ## APIs y pruebas - Postman: probar, documentar y automatizar APIs fácilmente. - Swagger (OpenAPI): documentación interactiva y estándar de APIs REST. ## Recursos adicionales - Imágenes gratis: Unsplash, Pexels, Pixabay. - Tipografías libres: Fontshare. - Wireframes rápidos: Mockplus y Wireframe.cc. ## Cómo integrar estas herramientas en tu flujo de trabajo 1. Define tu stack base → VS Code + GitHub + ClickUp. 2. Automatiza tareas repetitivas → GitHub Actions o scripts. 3. Centraliza la documentación → Notion o GitHub Wiki. 4. Optimiza diseño → prototipos en Figma, recursos de Canva y bancos de imágenes. 5. Mide y mejora constantemente → usa PageSpeed Insights y GTmetrix. ## Preguntas Frecuentes ### ¿Son suficientes las herramientas gratuitas para proyectos profesionales? Sí. Muchas startups y empresas consolidadas comienzan con herramientas gratuitas y escalan a planes pagos solo cuando crece el proyecto. ### ¿Qué riesgos existen al depender de software gratuito? Limitaciones en funciones avanzadas, menor soporte y cambios en políticas. Lo mejor es apostar por herramientas con comunidades activas y modelos sostenibles. ### ¿Cómo mantenerse actualizado con nuevas herramientas? Sigue newsletters de desarrollo, comunidades en GitHub, foros como Stack Overflow y blogs especializados. ### ¿Qué editores online son mejores para prototipar rápido? StackBlitz y CodeSandbox son opciones rápidas, ligeras y colaborativas. ### ¿Qué herramientas ayudan a mejorar el SEO técnico de una web? Google PageSpeed Insights, GTmetrix y Cloudflare son imprescindibles. ### ¿Vale la pena aprender Tailwind CSS en 2025? Sí. Es uno de los frameworks CSS más demandados gracias a su enfoque utility-first y alta personalización. ## Conclusión La mayoría de estas herramientas tienen planes gratuitos funcionales que permiten trabajar a nivel profesional sin inversión inicial. La clave está en elegir las que encajen con tu flujo de trabajo actual y agregar nuevas gradualmente según las necesidades del proyecto. Si quieres profundizar, la [guía oficial de GitHub para desarrolladores](https://docs.github.com/) tiene recursos, tutoriales y casos de uso prácticos. --- ### Apple Inc vs Apple Corps: el conflicto legal que redefinió el sonido digital - URL: https://www.angelcruz.dev/post/apple-inc-vs-apple-corps-historia - Markdown: https://www.angelcruz.dev/post/apple-inc-vs-apple-corps-historia.md - Categoría: Opinión - Fecha: 2025-08-29 - Excerpt: Descubre la historia de Apple Inc vs Apple Corps, la disputa legal entre los Beatles y la empresa de Steve Jobs que marcó un antes y un después en la relación entre música y tecnología. --- title: "Apple Inc vs Apple Corps: el conflicto legal que redefinió el sonido digital" excerpt: "Descubre la historia de Apple Inc vs Apple Corps, la disputa legal entre los Beatles y la empresa de Steve Jobs que marcó un antes y un después en la relación entre música y tecnología." date: "2025-08-29T01:16:40.000Z" category: "Opinión" seo_title: "Apple Inc vs Apple Corps: el conflicto legal de tres décadas" seo_description: "La disputa legal entre Apple y los Beatles duró casi 30 años. Del acuerdo de 1978 al Sosumi de Jim Reekes, hasta la llegada de los Beatles a iTunes en 2010." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ## Un nombre, dos mundos Cuando dos gigantes comparten nombre pero no propósito, el conflicto es casi inevitable. **Apple Inc.**, la empresa tecnológica fundada por **Steve Jobs** en 1976, y **Apple Corps**, el sello discográfico creado por **The Beatles** en 1968, protagonizaron una de las disputas legales más singulares entre música y tecnología. Apple Corps nació como parte del imperio creativo de los Beatles, mientras que Apple Computer (hoy Apple Inc.) se convirtió en pionera de la informática personal. En 1978, **Apple Corps demandó a Apple Computer**, alegando que el uso del nombre “Apple” en el ámbito musical violaba sus derechos. El acuerdo inicial fue claro: Apple Computer podía usar el nombre siempre que no se involucrara en la industria musical ([BBC News](http://news.bbc.co.uk/2/hi/business/6333635.stm)). Sin embargo, con la evolución tecnológica, esa frontera pronto se volvió difusa. ## Cuando las computadoras empezaron a sonar En los años 80, Apple Computer incorporó capacidades de audio en sus equipos. El **Apple II** ya podía reproducir tonos simples, y el **Macintosh** añadió sonidos de sistema diseñados con una intención estética. Apple Corps consideró que esto violaba el acuerdo, y en 1989 presentó una nueva demanda ([The Guardian](https://www.theguardian.com/technology/2006/may/08/news.thebeatles)). Fue en este contexto que el ingeniero de Apple **Jim Reekes** creó un sonido llamado *Chimes*. Los abogados lo rechazaron por sonar “demasiado musical”. Frustrado, Reekes renombró el archivo como **Sosumi**, un juego fonético con la frase *so sue me* (“entonces, demándame”). El truco funcionó, y *Sosumi* se convirtió en uno de los sonidos más icónicos de los sistemas Mac ([Wired](https://www.wired.com/2005/02/sosumi/)). ## El acuerdo final y la reconciliación digital La disputa entre Apple Inc. y Apple Corps se prolongó durante décadas, con múltiples demandas y acuerdos intermedios. Finalmente, en **2007 ambas compañías llegaron a un acuerdo definitivo**: - Apple Inc. adquirió todos los derechos sobre el nombre “Apple”. - Apple otorgó una licencia a Apple Corps para seguir usándolo. En un giro simbólico, en **2010 la música de los Beatles llegó a iTunes**, cerrando un ciclo de tensiones entre ambas Apples y marcando un punto de reconciliación digital ([Apple Press Release](https://www.apple.com/newsroom/2010/11/16iTunes-to-Offer-The-Beatles-Catalog/)). ## Más allá de las manzanas: otras disputas entre marcas y sonidos El conflicto entre Apple Inc. y Apple Corps no fue el único que unió música y tribunales. - **Metallica vs. Napster (2000):** la banda demandó a la plataforma de intercambio de archivos, marcando el inicio de la era del debate sobre distribución digital ([Rolling Stone](https://www.rollingstone.com/music/music-news/napster-vs-metallica-the-lawsuit-that-rocked-the-music-industry-181144/)). - **Sonidos registrados como marcas:** el rugido de **MGM**, el tono de arranque de **Intel**, o incluso el clic de cámara de **Instagram** han sido objeto de protección legal ([USPTO](https://www.uspto.gov/)). Estas batallas muestran cómo los **sonidos y marcas** no solo comunican identidad, sino que también definen territorios comerciales. En un mundo donde una nota puede valer millones, el silencio legal rara vez dura mucho. El caso **Apple Inc vs Apple Corps** muestra cómo dos industrias distintas pueden chocar en los tribunales y, a la larga, encontrar un acuerdo. La disputa duró casi tres décadas y se resolvió, paradójicamente, cuando la música de los Beatles llegó a iTunes en 2010. --- ### Bye Bye GitHub: ¿Fin de una Era o Comienzo de Otra? - URL: https://www.angelcruz.dev/post/bye-bye-github - Markdown: https://www.angelcruz.dev/post/bye-bye-github.md - Categoría: Opinión - Fecha: 2025-08-17 - Excerpt: GitHub cambia para siempre: Thomas Dohmke deja el CEO, Microsoft no lo reemplaza y la plataforma se integra al equipo CoreAI. Qué significa esto para ti. --- title: "Bye Bye GitHub: ¿Fin de una Era o Comienzo de Otra?" excerpt: "GitHub cambia para siempre: Thomas Dohmke deja el CEO, Microsoft no lo reemplaza y la plataforma se integra al equipo CoreAI. Qué significa esto para ti." date: "2025-08-17T19:59:57.000Z" category: "Opinión" seo_title: "GitHub se integra en Microsoft CoreAI — fin de su autonomía" seo_description: "Thomas Dohmke deja el CEO de GitHub y la plataforma pasa a depender del equipo CoreAI de Microsoft. Análisis del impacto para desarrolladores y las alternativas como Codeberg." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![Github](https://github.blog/wp-content/uploads/2025/03/github_logo_invertocat_dark_5.png?w=1024) Desde su adquisición en 2018, GitHub mantuvo una relativa autonomía. Bajo Dohmke, la plataforma creció enormemente: impulsó GitHub Copilot, mejoró herramientas como Actions y Codespaces, y se consolidó como el corazón del desarrollo colaborativo. Pero con su integración en CoreAI, la toma de decisiones técnicas, estratégicas y culturales ya no será interna. Ahora, Microsoft dicta el rumbo. ## ¿Por qué no fue absorbido por Azure? Aunque GitHub tiene vínculos con Azure, su valor clave no es la infraestructura, sino el flujo cognitivo: es donde los desarrolladores piensan, colaboran y crean. Para entrenar modelos como Copilot, CoreAI necesita más que datos: necesita contexto, lenguaje humano e interacción, y GitHub es el entorno ideal para eso. ## Reacciones divididas La carta de despedida de Dohmke provocó sentimientos encontrados. Mientras algunos celebran los avances, otros vieron en sus palabras una despedida simbólica. En redes sociales, el hashtag #ByeByeGitHub comenzó a circular junto con memes, críticas y reflexiones sobre el futuro del desarrollo abierto. ## ¿Habrá despidos? En 2025, Microsoft ha ejecutado varias rondas de despidos. Aunque GitHub no ha sido confirmado como afectado, la absorción por CoreAI y la nueva estrategia de IA aumentan el riesgo, especialmente en áreas no alineadas con ese enfoque. ## ¿Qué viene ahora? GitHub seguirá funcionando, pero su esencia cambia. Se prevé mayor integración de inteligencia artificial, más automatización y nuevas herramientas predictivas. Esto podría mejorar la eficiencia, pero también limitar la autonomía y visibilidad de los desarrolladores humanos. ## Alternativas emergentes El cambio ha impulsado el interés en plataformas más comunitarias y descentralizadas. Gitea, Codeberg y proyectos como Radicle ofrecen alternativas más alineadas con los principios del software libre y colaborativo. Bye Bye GitHub no es solo una despedida simbólica. Es un grito de atención, una señal de que el ecosistema del desarrollo de software está entrando en una nueva etapa, donde la inteligencia artificial y la centralización jugarán un papel más fuerte. GitHub seguirá siendo importante, quizá más eficiente e inteligente que nunca. Pero el espíritu con el que muchos lo conocieron —comunidad abierta, libertad de colaboración y decisión— ya no será el mismo. Es momento de reflexionar: ¿queremos ser usuarios de herramientas que evolucionan según intereses corporativos o queremos formar parte activa de plataformas que reflejen nuestros valores como desarrolladores? > Ese futuro lo decidirá, como siempre, la comunidad. ## Enlaces de interes - [GitHub Just Got Assimilated – CEO Out, Copilot In, Microsoft All Over It](https://www.windowscentral.com/microsoft/github-ceo-resigns-ending-independent-operations-as-platform-joins-microsofts-coreai-division?utm_source=chatgpt.com) *Windows Central confirma la salida de Thomas Dohmke y la integración completa de GitHub dentro del equipo CoreAI de Microsoft.* - [GitHub folds into Microsoft following CEO resignation](https://www.tomshardware.com/software/programming/github-folds-into-microsoft-following-ceo-resignation-once-independent-programming-site-now-part-of-coreai-team?utm_source=chatgpt.com) *Tom’s Hardware informa sobre la pérdida de autonomía de GitHub y su nueva posición dentro de Microsoft.* - [GitHub just got less independent at Microsoft after CEO resignation](https://www.theverge.com/news/757461/microsoft-github-thomas-dohmke-resignation-coreai-team-transition?utm_source=chatgpt.com) *The Verge explica cómo el liderazgo de GitHub pasará a depender directamente de CoreAI, sin nuevo CEO.* - [Exclusive: GitHub CEO to step down](https://www.axios.com/2025/08/11/github-ceo-dohmke-step-down?utm_source=chatgpt.com) *Axios revela en exclusiva que Thomas Dohmke dejará su cargo y que no habrá reemplazo para su puesto.* - [GitHub CEO Thomas Dohmke to step down, plans new startup](https://www.reuters.com/sustainability/boards-policy-regulation/github-ceo-thomas-dohmke-step-down-plans-new-startup-2025-08-11/?utm_source=chatgpt.com) *Reuters detalla que Dohmke planea volver al mundo de las startups tras dejar GitHub.* - [GitHub will join Microsoft’s CoreAI division with departure of CEO Thomas Dohmke](https://www.geekwire.com/2025/github-will-join-microsofts-coreai-group-with-departure-of-ceo-thomas-dohmke/?utm_source=chatgpt.com) *GeekWire aporta contexto sobre la estrategia de IA de Microsoft y cómo GitHub encaja en ella.* --- ### Guía Completa para Crear Reglas en Cursor (Incluye Herramienta Online) - URL: https://www.angelcruz.dev/post/crear-reglas-cursor-ide - Markdown: https://www.angelcruz.dev/post/crear-reglas-cursor-ide.md - Categoría: Herramientas - Fecha: 2025-07-06 - Excerpt: Aprende cómo crear reglas personalizadas en Cursor paso a paso. Incluye ejemplos, mejores prácticas y un generador de reglas online para facilitar el proceso. --- title: "Guía Completa para Crear Reglas en Cursor (Incluye Herramienta Online)" excerpt: "Aprende cómo crear reglas personalizadas en Cursor paso a paso. Incluye ejemplos, mejores prácticas y un generador de reglas online para facilitar el proceso." date: "2025-07-06T20:02:30.000Z" category: "Herramientas" seo_title: "Crear reglas en Cursor IDE: guía completa y generador online" seo_description: "Crea reglas efectivas en Cursor con archivos .mdc en .cursor/rules. Guía paso a paso con tipos Always, Auto Attached, Agent Requested y acceso al generador online gratuito." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/cursor-og-image.png" --- ## Introducción Cursor permite definir reglas contextuales para mejorar la asistencia de IA durante la programación. Estas reglas ayudan a mantener consistencia, estilo y buenas prácticas en tus proyectos. En esta guía, exploraremos cómo crear reglas efectivas, cuándo usarlas y cómo aprovechar herramientas como el generador online de reglas para Cursor. Y si todavía estás evaluando la herramienta, revisa primero los [precios y planes de Cursor](/post/cursor-ide-precios-planes) para elegir el tier que se ajusta a tu uso. ## ¿Qué son las Reglas de Cursor? Las reglas son documentos que ayudan al agente de IA de Cursor a entender el contexto de tu código. Existen varios tipos de reglas: - **Always**: Siempre aplicadas. - **Auto Attached**: Se activan automáticamente según patrones de archivos. - **Agent Requested**: Sugeridas por la IA. - **Manual**: Activadas manualmente usando `@ruleName`. ## Estructura y Ubicación Las reglas se guardan en `.cursor/rules` con formato `.mdc`. Cada archivo puede tener metadata como: ```yaml --- description: "Ejemplo de regla" globs: - "src/**/*.ts" alwaysApply: true --- ``` Y debajo, el contenido que describe qué debe hacer el desarrollador. ## Ejemplo Práctico ```yaml --- description: "Usar snake_case en servicios" globs: - "backend/**/*.ts" alwaysApply: false --- - Los nombres de función deben seguir el formato snake_case. ``` ## Crear Reglas desde Cursor Puedes usar la interfaz de Cursor (`Settings > Rules > New Rule`) o el comando `/Generate Cursor Rules` para generar contenido automáticamente. ## Herramientas para Generar Reglas Además de escribir reglas manualmente, puedes usar generadores automáticos. Una opción destacada es el siguiente generador online, muy útil para quienes desean evitar errores de sintaxis: 👉 [Generador de Reglas para Cursor](https://angelcruz.dev/tools/cursor-rules-generator-online) Esta herramienta te permite crear archivos `.mdc` personalizados a partir de un formulario sencillo. Es ideal para acelerar el proceso de configuración y garantizar que tus reglas estén correctamente estructuradas. ## Mejores Prácticas - Mantén reglas por debajo de 500 líneas. - Usa ejemplos concretos. - Organiza las reglas en carpetas temáticas (frontend, backend, tests). - Documenta el propósito de cada regla claramente. ## Preguntas Frecuentes ### ¿Puedo usar variables en las reglas? No directamente, pero puedes estructurarlas para que se apliquen por patrones. ### ¿Qué diferencia hay entre Auto Attached y Always? Auto Attached depende del archivo; Always se aplica globalmente. ### ¿Cómo actualizo reglas sin reiniciar Cursor? Al guardar el archivo `.mdc`, Cursor detecta el cambio automáticamente. ### ¿Se pueden heredar reglas entre proyectos? No directamente, pero puedes copiar la carpeta `.cursor/rules`. ### ¿Puedo usar la IA para generar reglas? Sí, con el comando `/Generate Cursor Rules` en el chat de Cursor. ### ¿Qué pasa si tengo reglas duplicadas? La regla más específica tiene prioridad según el patrón (`globs`). ## Conclusión Crear reglas en Cursor es clave para mantener la coherencia y la calidad del código. Con herramientas como [este generador online](https://angelcruz.dev/tools/cursor-rules-generator-online), el proceso es más rápido y menos propenso a errores. Empieza hoy a crear reglas efectivas y verás cómo mejora tu flujo de trabajo. **Referencia oficial**: [Documentación de Reglas en Cursor](https://docs.cursor.com/context/rules) --- ### ¿Qué es MCP? El Protocolo que Revoluciona el Desarrollo de Agentes Inteligentes - URL: https://www.angelcruz.dev/post/introduccion-a-mcp-model-context-protocol - Markdown: https://www.angelcruz.dev/post/introduccion-a-mcp-model-context-protocol.md - Categoría: Inteligencia Artificial - Fecha: 2025-07-03 - Excerpt: MCP (Model Context Protocol) es el estándar que permite a modelos de lenguaje como Claude o GPT conectarse a herramientas externas de forma modular y segura. Aprende cómo funciona su arquitectura y cuándo usarlo en tus proyectos de IA. --- title: "¿Qué es MCP? El Protocolo que Revoluciona el Desarrollo de Agentes Inteligentes" excerpt: "MCP (Model Context Protocol) es el estándar que permite a modelos de lenguaje como Claude o GPT conectarse a herramientas externas de forma modular y segura. Aprende cómo funciona su arquitectura y cuándo usarlo en tus proyectos de IA." date: "2025-07-03T06:00:00.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "Inteligencia Artificial" seo_title: "MCP: el protocolo estándar para conectar LLMs a herramientas externas" seo_description: "Model Context Protocol (MCP) es el estándar que conecta modelos como Claude o GPT a herramientas externas de forma modular y segura. Compatible con GPT, Claude y Gemini." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/mcp-opengraph-image.png" --- ## ¿Qué es MCP y cómo funciona? Los agentes de inteligencia artificial como GPT, Claude o Gemini pueden realizar tareas complejas, pero ¿cómo interactúan con herramientas externas, servicios o bases de datos? La respuesta es el **Model Context Protocol (MCP)**, un estándar que organiza esta comunicación de forma **estructurada, segura y escalable**. MCP permite que los modelos de lenguaje accedan a funciones externas sin necesidad de codificar todo dentro del prompt. En su lugar, propone una arquitectura modular que separa claramente cada responsabilidad del sistema. ## ¿Cómo funciona MCP? La arquitectura de MCP se compone de tres elementos principales: - **El modelo**: interpreta las entradas del usuario y toma decisiones. - **El servidor MCP**: es el intermediario que gestiona la comunicación y la seguridad. - **Las herramientas**: funciones externas (como enviar correos, consultar APIs, leer archivos, etc.). ![MCP Flow](https://angelcruz.dev/storage/article/VSoS82Xe2I5e1Wci37iA3P1YuwNPiiNOcY6FOaIh.jpg) Toda la interacción se gestiona a través de un protocolo estructurado (como JSON-RPC), lo que permite un diseño desacoplado pero bien coordinado. ## ¿Qué es el manifiesto en MCP? El **manifiesto** es un archivo clave (usualmente en formato JSON) que documenta todas las herramientas disponibles: - **Define** qué herramientas existen, qué hacen y qué parámetros aceptan. - **Guía** al modelo sobre cómo solicitar esas herramientas. - **Valida** que las llamadas sean correctas y seguras. Gracias a este documento, la integración de nuevas funcionalidades se vuelve mucho más sencilla, sin afectar la estructura existente del sistema. ## ¿Por qué es diferente a otros métodos? Antes, los desarrolladores usaban prompts complejos o código manual para conectar modelos con herramientas. Con MCP: - Las herramientas se **declaran explícitamente** en un manifiesto. - Los modelos pueden **solicitar su uso** sin conocer su implementación interna. - Se puede trabajar con múltiples modelos y entornos sin fricción. En resumen, MCP permite pasar de soluciones improvisadas a una **arquitectura estandarizada y profesional**. ## ¿Cuándo es conveniente usar MCP? ### Es útil si: - Necesitas que tu modelo se comunique con múltiples herramientas externas. - Requieres **seguridad, trazabilidad o control de acceso**. - Operas en **entornos colaborativos** o con varios agentes simultáneos. ### No es necesario si: - Estás desarrollando un agente que realiza tareas muy simples. - No necesitas modularidad ni una integración compleja. ## ¿Qué permite MCP a futuro? MCP no solo resuelve problemas actuales. También prepara el terreno para el desarrollo de agentes más avanzados. Con MCP puedes: - Crear **ecosistemas de agentes colaborativos**. - Compartir contexto entre modelos. - Integrar herramientas como módulos intercambiables. - Establecer una base sólida para escalar tu solución. Además, abre la posibilidad de estándares complementarios (como Agent2Agent o ETDI) que promueven interoperabilidad, seguridad extendida y gestión distribuida de herramientas. ## Preguntas Frecuentes ### ¿Qué significa MCP en inteligencia artificial? Es el *Model Context Protocol*, un estándar que permite a modelos de lenguaje utilizar herramientas externas de manera controlada y modular. ### ¿MCP es compatible con todos los modelos? Sí. Se ha diseñado para ser independiente del modelo, y ya es compatible con GPT, Claude, Gemini y más. ### ¿Es obligatorio implementar un servidor MCP? Sí, es necesario para gestionar las solicitudes del modelo y ejecutar herramientas con seguridad. ### ¿Qué beneficios ofrece el manifiesto? Centraliza la documentación, facilita el desarrollo, y garantiza que las herramientas se usen correctamente. ### ¿Puede usarse MCP en entornos empresariales? Sí. De hecho, es ideal para aplicaciones que exigen control de acceso, trazabilidad y colaboración. ### ¿Dónde puedo aprender más sobre MCP? Puedes visitar el [sitio oficial del protocolo MCP](https://modelcontextprotocol.io) para más recursos, SDKs y documentación. ## Conclusión El **Model Context Protocol (MCP)** es una herramienta útil para el desarrollo de agentes inteligentes. Su enfoque modular, seguro y estandarizado permite crear soluciones de IA más trazables y escalables. Si estás construyendo con IA, **MCP es el puente entre tus modelos y tus herramientas externas**. --- ### Testing de modelos en Laravel: ¿necesario o no? Una mirada crítica y práctica - URL: https://www.angelcruz.dev/post/laravel-testing-modelos-si-o-no - Markdown: https://www.angelcruz.dev/post/laravel-testing-modelos-si-o-no.md - Categoría: Laravel - Fecha: 2025-06-30 - Excerpt: ¿Vale la pena hacer testing de modelos en Laravel? Análisis crítico y práctico: ventajas, cuándo es necesario, errores comunes y mejores prácticas con PHPUnit, Pest y factories. --- title: "Testing de modelos en Laravel: ¿necesario o no? Una mirada crítica y práctica" excerpt: "¿Vale la pena hacer testing de modelos en Laravel? Análisis crítico y práctico: ventajas, cuándo es necesario, errores comunes y mejores prácticas con PHPUnit, Pest y factories." date: "2025-06-30T06:00:00.000Z" category: "Laravel" seo_title: "Testing de modelos en Laravel: cuándo es necesario y cuándo no" seo_description: "Análisis práctico sobre cuándo testear modelos en Laravel con PHPUnit y Pest. Ventajas, errores comunes y buenas prácticas con factories para modelos con lógica compleja." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ## Introducción al testing en Laravel Laravel es un framework que fomenta las buenas prácticas, y entre ellas, el testing ocupa un lugar crucial. A menudo se habla de pruebas unitarias, de integración o funcionales, pero el testing de modelos suele quedar en un terreno ambiguo. Este artículo busca responder la pregunta clave: *¿es realmente necesario testear los modelos en Laravel?* ### ¿Qué es el testing automatizado en desarrollo web? El testing automatizado consiste en crear scripts que verifiquen automáticamente que el comportamiento de una aplicación es el esperado. En Laravel, esto se logra principalmente con PHPUnit y el framework de pruebas integrado. ### Tipos de pruebas en Laravel: Unitarias, funcionales e integradas - **Unitarias:** Evalúan funciones pequeñas y específicas (por ejemplo, un método de un modelo). - **Funcionales:** Verifican acciones de usuario (por ejemplo, crear un usuario a través del formulario). - **Integradas:** Comprueban que varios componentes trabajan correctamente en conjunto. ## ¿Qué son los modelos en Laravel y por qué son importantes? Los modelos en Laravel representan las entidades de tu base de datos. Son esenciales porque conectan los datos con la lógica del negocio. ### Estructura de un modelo típico en Laravel Un modelo define atributos `$fillable`, relaciones, scopes, mutadores y otras funcionalidades que definen el comportamiento del dato. ### Relaciones, scopes y lógica de negocio Los modelos pueden contener relaciones como `hasMany` o `belongsTo`, scopes personalizados como `active()` y métodos que encapsulan lógica crítica. ## ¿Qué implica hacer testing de modelos? El testing de modelos va más allá de verificar que existan o que funcionen con la base de datos. Involucra probar su comportamiento ante distintos escenarios. ### Testing de atributos, relaciones y métodos personalizados Es clave testear: - Que las relaciones funcionen correctamente. - Que los métodos personalizados devuelvan los datos correctos. - Que los scopes actúen como se espera. ### Testing de reglas de validación y casting Laravel permite definir casts como `boolean`, `date`, `json`, y es importante verificar que esos comportamientos se mantengan bajo cambios. ## Ventajas del testing de modelos en Laravel ### Reducción de errores en producción Testear modelos evita que pequeños cambios en relaciones o mutadores rompan funcionalidades en producción. ### Mayor seguridad al refactorizar Con pruebas, puedes cambiar la estructura interna de un modelo sin miedo a romper su funcionamiento. ### Documentación viva de las expectativas del modelo Los tests actúan como una forma viviente de documentación sobre cómo debe comportarse el modelo. ## ¿Cuándo puede no ser necesario testear los modelos? ### Modelos simples y sin lógica personalizada Si tu modelo solo define una tabla y no contiene lógica compleja, podría no ser necesario escribir tests directos. ### Casos en los que otros tipos de pruebas cubren el comportamiento Si los controladores o pruebas de integración ya están validando la funcionalidad del modelo, puede evitarse la redundancia. ## Buenas prácticas para testear modelos en Laravel ### Uso de factories y seeders para generar datos Laravel facilita la creación de modelos con datos realistas usando factories y seeders, lo que permite mantener los tests limpios y coherentes. ### Estructura ideal de los test cases de modelo - Crear tests para cada relación. - Validar que los casts sean correctos. - Probar métodos personalizados con múltiples escenarios. ### Uso de PHPUnit y Laravel Pest Pest permite escribir pruebas más legibles con una sintaxis fluida. Combinado con PHPUnit, crea un entorno robusto de testing. ## Errores comunes al testear modelos y cómo evitarlos ### Testear código generado automáticamente Evita probar funciones de Eloquent ya probadas por Laravel, como `save()` o `find()`. Concéntrate en tu propia lógica. ### Pruebas demasiado acopladas a la implementación Testea el comportamiento, no la implementación interna. Así tus pruebas siguen funcionando tras cambios internos. ## Herramientas y librerías recomendadas ### Laravel Test Factories Permiten crear instancias de modelos con facilidad para múltiples escenarios. ### Laravel Pest, Mockery y otras extensiones - **Pest** para sintaxis simple. - **Mockery** para simular dependencias externas. ## Casos reales: Cuándo el testing de modelos salvó un proyecto En múltiples proyectos, los tests han evitado que errores sutiles en scopes o relaciones incorrectas pasen a producción, especialmente en proyectos que evolucionan rápidamente. ## Opiniones de la comunidad: ¿Vale la pena? ### Encuestas en Twitter, Reddit y Laracasts Las opiniones están divididas. Muchos desarrolladores creen que si el modelo tiene lógica de negocio, debe testearse. Otros confían en las pruebas de integración. ## Conclusión: ¿Testing de modelos, sí o no? Testear modelos en Laravel no es obligatorio en todos los casos, pero es altamente recomendable cuando contienen lógica compleja, relaciones críticas o métodos personalizados. La clave está en identificar cuándo el modelo agrega valor al negocio y asegurarse de que su comportamiento esté protegido. ## Preguntas Frecuentes ### ¿Debo testear modelos que solo tienen relaciones básicas? Solo si esas relaciones son esenciales para el flujo de negocio. Si no, las pruebas de integración podrían ser suficientes. ### ¿Es redundante testear modelos si ya tengo pruebas funcionales? No necesariamente. Las pruebas funcionales verifican flujos completos, pero los tests de modelo aseguran la lógica interna. ### ¿Cuándo es mejor usar Pest en vez de PHPUnit? Pest es ideal para tests más legibles y rápidos de escribir, pero PHPUnit es más flexible en tests avanzados. ### ¿Cuántos tests son suficientes para un modelo? Lo justo para cubrir todos sus comportamientos críticos. No es cuestión de cantidad, sino de cobertura efectiva. ### ¿Qué errores comunes debo evitar al testear modelos? Testear funciones de Laravel que ya están probadas, o acoplar el test demasiado a la implementación interna. ### ¿Dónde puedo aprender más sobre testing en Laravel? Puedes comenzar en la [documentación oficial de Laravel](https://laravel.com/docs/testing) o explorar cursos en Laracasts. --- ### Laravel Nightwatch Cambia las Reglas: Monitoreo y Logs sin Dolor - URL: https://www.angelcruz.dev/post/laravel-nightwatch-monitoreo - Markdown: https://www.angelcruz.dev/post/laravel-nightwatch-monitoreo.md - Categoría: Laravel - Fecha: 2025-06-19 - Excerpt: Laravel Nightwatch es el servicio de monitoreo y observabilidad diseñado exclusivamente para Laravel, anunciado en junio de 2025. Dashboard en tiempo real, historial de errores, monitoreo de jobs y comparativa con Sentry. --- title: "Laravel Nightwatch Cambia las Reglas: Monitoreo y Logs sin Dolor" excerpt: "Laravel Nightwatch es el servicio de monitoreo y observabilidad diseñado exclusivamente para Laravel, anunciado en junio de 2025. Dashboard en tiempo real, historial de errores, monitoreo de jobs y comparativa con Sentry." date: "2025-06-19T11:00:00.000Z" category: "Laravel" seo_title: "Laravel Nightwatch vs Sentry: monitoreo nativo para Laravel" seo_description: "Laravel Nightwatch es el servicio de observabilidad exclusivo para Laravel lanzado en 2025. Dashboard en tiempo real, monitoreo de jobs, errores y recursos del servidor desde un solo lugar." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ## ¿Qué es Laravel Nightwatch? Laravel Nightwatch es el servicio de **observabilidad y monitoreo en tiempo real** de Laravel, anunciado en junio de 2025. Está diseñado exclusivamente para aplicaciones Laravel, a diferencia de herramientas genéricas como Sentry, Bugsnag o Datadog. En este artículo se cubre qué ofrece Nightwatch, cómo se instala y cuándo tiene sentido usarlo sobre otras alternativas. ## Características y objetivo ### Qué hace Nightwatch Laravel Nightwatch es un servicio SaaS que permite monitorear el rendimiento, errores, procesos en segundo plano (jobs) y mucho más de tu aplicación Laravel en tiempo real. A diferencia de herramientas genéricas, Nightwatch ha sido diseñado exclusivamente para Laravel, lo cual permite una integración nativa sin complicaciones. ### Lanzamiento oficial y novedades Anunciado oficialmente el 16 de junio de 2025, Nightwatch llega con características diseñadas para facilitar la vida del desarrollador: - Dashboard limpio y en tiempo real. - Historial de errores con contexto completo. - Monitoreo del servidor: CPU, memoria y disco. - Timeline detallado de cada request y job. ## ¿Para quién es Laravel Nightwatch? ### Casos de uso típicos Laravel Nightwatch es ideal para: - Aplicaciones SaaS hechas completamente en Laravel. - Proyectos en producción que necesiten trazabilidad. - Equipos que usen Laravel Forge o Vapor. ### Cuándo usar Nightwatch sobre otras herramientas Si tu stack es 100% Laravel, usar Nightwatch reduce la complejidad de integración y te ofrece datos más relevantes que otras herramientas más genéricas como Sentry o LogRocket. ## Principales características de Laravel Nightwatch ### Panel centralizado con datos de rendimiento Desde el dashboard puedes ver: - Rutas más utilizadas. - Jobs en segundo plano. - Memoria consumida por petición. - Promedios de respuesta HTTP. ### Registro y contexto de errores Nightwatch te muestra los errores agrupados por tipo, URL, usuario y clase. Así puedes ver cuándo y por qué se produce cada excepción. ### Monitorización de servidor y recursos No necesitas Prometheus o Datadog para ver cuánto CPU o memoria consume tu servidor. Nightwatch ya lo hace por ti desde su interfaz web. ## Cómo instalar Laravel Nightwatch paso a paso ### Requisitos previos y token de configuración Antes de comenzar, debes: 1. Crear una cuenta en [nightwatch.laravel.com](https://nightwatch.laravel.com). 2. Crear una aplicación y obtener tu token. 3. Tener Laravel 10 o superior instalado. ### Comandos esenciales para activación ```bash composer require laravel/nightwatch php artisan nightwatch:install php artisan nightwatch:agent ``` Configura las variables en tu `.env`: ``` NIGHTWATCH_TOKEN=tu_token LOG_CHANNEL=nightwatch NIGHTWATCH_SAMPLE_RATE=100 ``` ## Laravel Nightwatch vs Sentry: Comparativa directa ![Laravel Nightwatch vs Sentry: Comparativa directa](https://angelcruz.dev/storage/article/ZFDm65vF5zhTanxVFrLSpU8dsYJuE0SCW5UcawT2.png) ## Beneficios únicos de Laravel Nightwatch ### Integración profunda con el núcleo Laravel Nightwatch entiende eventos internos como `JobProcessed`, `QueryExecuted` o `ExceptionOccurred`, por lo que ofrece información específica que otras plataformas no pueden ver sin configuración adicional. ### Alertas inteligentes y personalizadas Puedes configurar alertas según número de excepciones por minuto, tiempos de respuesta lentos o errores específicos. ## Limitaciones y puntos a mejorar - No ofrece soporte para otros lenguajes como Node.js o Ruby. - Requiere que ejecutes un agente con `php artisan`. - Solo se puede usar con Laravel 10 o superior. ## Planes y precios de Laravel Nightwatch ![Plan gratuito vs plan premium](https://angelcruz.dev/storage/article/mZClYHiMwRxdnLTBE8JHYBrFWXo3G7nF2TBimhiw.png) ## Opiniones y feedback de la comunidad Varios desarrolladores han reportado migraciones desde Sentry o Ray a Nightwatch, destacando que la información que proporciona es más relevante para stacks Laravel-only. ## ¿Nightwatch o Sentry para tu proyecto? Si estás construyendo con Laravel, la elección lógica es **Laravel Nightwatch**. Te da exactamente lo que necesitas, sin el esfuerzo de configurar herramientas externas. Pero si trabajas con múltiples stacks o necesitas integraciones más amplias, entonces **Sentry** sigue siendo una gran elección. ## Preguntas Frecuentes ### ¿Nightwatch funciona con Laravel Forge o Vapor? Sí. De hecho, la integración con Laravel Forge y Vapor es automática. ### ¿Requiere mucha configuración inicial? No. Solo necesitas instalar el paquete, configurar el token y ejecutar el agente. ### ¿Qué tan seguro es enviar mis logs a un servidor externo? Laravel Nightwatch cifra los datos y cumple con políticas de privacidad y seguridad modernas. ### ¿Qué pasa si mi app no es Laravel puro? Nightwatch está diseñado exclusivamente para Laravel, por lo que no funcionará en otros entornos. ### ¿Nightwatch consume muchos recursos? El agente es liviano y está optimizado para producción. ### ¿Puede reemplazar por completo a herramientas como Sentry? Para apps Laravel, sí. Para stacks mixtos, Sentry sigue teniendo ventajas. --- ### Diseño atómico en Laravel: guía básica para componentes reutilizables - URL: https://www.angelcruz.dev/post/componentes-reutilizables-laravel - Markdown: https://www.angelcruz.dev/post/componentes-reutilizables-laravel.md - Categoría: Laravel - Fecha: 2025-06-13 - Excerpt: Guía práctica para implementar Atomic Design en Laravel usando Blade Components. Aprende a organizar átomos, moléculas y organismos en una estructura de carpetas escalable y mantenible. --- title: "Diseño atómico en Laravel: guía básica para componentes reutilizables" excerpt: "Guía práctica para implementar Atomic Design en Laravel usando Blade Components. Aprende a organizar átomos, moléculas y organismos en una estructura de carpetas escalable y mantenible." date: "2025-06-13T06:00:00.000Z" category: "Laravel" seo_title: "Atomic Design en Laravel: componentes Blade reutilizables" seo_description: "Implementa la metodología Atomic Design en Laravel usando Blade Components. Estructura átomos, moléculas y organismos en carpetas escalables para proyectos de cualquier tamaño." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ## Introducción a Atomic Design Atomic Design es una metodología creada por Brad Frost que organiza interfaces en cinco niveles: **átomos, moléculas, organismos, templates y páginas**. Este enfoque modular promueve la coherencia visual y facilita el mantenimiento en proyectos de UI. Cuando se aplica a Laravel, permite estructurar componentes Blade de manera lógica y escalable, fomentando una interfaz limpia y organizada. ### ¿Por qué Atomic Design? Adoptar Atomic Design trae beneficios claros: - Reutilización: construir interfaces a partir de bloques pequeños y repetibles. - Mantenibilidad: cambios centralizados sin riesgos. - Escalabilidad: una base sólida al crecer. Usar este método en Laravel mejora la productividad y reduce errores. ## Fundamentos de Atomic Design Aquí desarrollamos cada nivel de la metodología: ### Átomos Son los componentes más básicos: botones, inputs, etiquetas… En Laravel se implementan como componentes Blade individuales. ### Moléculas Combinación de átomos para formar unidades funcionales, como un campo de búsqueda con etiqueta, input y botón. ### Organismos Son áreas completas de UI, como barras de navegación o tarjetas de producto, combinando varias moléculas. ### Templates y Páginas - **Templates**: estructuras de página con layout fijo y placeholders. - **Páginas**: instancias reales de templates con contenido concreto. ## Ventajas en Laravel --- ### Blade components Laravel Blade facilita la creación de componentes reutilizables (``), clasificándolos por funcionalidades según el nivel atómico. ### Service Providers y DI Permiten inyectar dependencias y lógica relacionada con componentes, aislando complejidad. ### Testeo y mantenibilidad Al separar UI en componentes pequeños, se facilitan pruebas unitarias y refactorizaciones. ## Pasos para Implementarlo Guía práctica en 4 pasos: ### Crear estructura de carpetas ```bash resources/views/ └── components/ ├── atoms/ ├── molecules/ └── organisms/ ``` ### Definir componentes Blade (átomo) Un botón podría ser un ejemplo sencillo: ```blade // resources/views/components/atoms/button.blade.php: ``` ### Combinar en moléculas Podría ser un input para buscar: ```blade // components/molecules/search.blade.php ``` ### En una vista Puede ser algo así: ```blade
``` ## Mejores prácticas --- ### Nombres consistentes Usa convenciones claras como atoms, molecules. ### Evitar duplicación Usa componentes construidos con bloques existentes. ### Documentar componentes Integra Storybook o comentarios Blade para facilitar adopción. ## Preguntas Frecuentes ### ¿Laravel ya soporta Atomic Design? No por defecto, pero Blade Components facilitan su implementación. ### ¿Conviene usar CSS-in-JS? No es necesario; puedes usar Laravel Mix con Sass. ### ¿Se puede aplicar sin Blade? Sí, pero con Livewire o Vue, también es posible. ### ¿Es adecuado para apps pequeñas? Sí, desde proyectos pequeños hasta grandes. ### ¿Cómo testear componentes? Con PHPUnit / PestPHP y Laravel Dusk puedes testear lógica y UI. ### ¿Dónde documentar los componentes? Usa herramientas como Storybook o Styleguidist. --- ### WordPress Studio: Guía Completa 2026 - URL: https://www.angelcruz.dev/post/que-es-wordpress-studio - Markdown: https://www.angelcruz.dev/post/que-es-wordpress-studio.md - Categoría: WordPress - Fecha: 2024-05-15 - Excerpt: WordPress Studio es la herramienta oficial gratuita de WordPress.com para desarrollo local. Disponible para macOS y Windows, incluye CLI, Blueprints, Xdebug, soporte PHP 8.5 y sincronización con WordPress.com. --- title: "WordPress Studio: Guía Completa 2026" excerpt: "WordPress Studio es la herramienta oficial gratuita de WordPress.com para desarrollo local. Disponible para macOS y Windows, incluye CLI, Blueprints, Xdebug, soporte PHP 8.5 y sincronización con WordPress.com." date: "2024-05-15T02:59:00.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "WordPress" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" seo_title: "WordPress Studio 2026: Qué Es, Cómo Instalarlo y Para Qué Sirve" seo_description: "Aprende a usar WordPress Studio: herramienta gratuita para desarrollo local en macOS y Windows. Blueprints, CLI 1.7, Xdebug y PHP 8.5. Guía actualizada marzo 2026." --- **WordPress Studio** es la herramienta oficial gratuita de WordPress.com para crear entornos de desarrollo local. Disponible para macOS y Windows, no requiere configurar Apache, NGINX ni MySQL: instalas la aplicación, presionas "Add site" y en segundos tienes un sitio WordPress funcionando con acceso directo a WP Admin y el Editor de Bloques. > **Última actualización:** Marzo 2026 — versión 1.7.x. Este artículo refleja las funcionalidades actuales incluyendo CLI, Blueprints, soporte Xdebug y PHP 8.5. ## ¿Qué es WordPress Studio? Studio es una aplicación de escritorio de código libre desarrollada por Automattic (la empresa detrás de WordPress.com). Usa **WordPress Playground** como base de ejecución, lo que elimina la necesidad de instalar dependencias del servidor manualmente. **Características principales:** - Entornos locales WordPress sin configuración de servidor - Acceso directo a WP Admin, Editor de Bloques y estilos globales - Compatible con plugins, temas y patrones - Sin usuario ni contraseña para acceder al sitio local - Completamente gratuito y de código abierto ## Disponibilidad: macOS y Windows Cuando Studio se lanzó en 2024, solo estaba disponible para Mac. **Desde mayo de 2024, hay versión para Windows** disponible en Microsoft Store y descarga directa. Más del 25% de los desarrolladores de WordPress usan Windows, por lo que esta versión amplió significativamente su alcance. Descarga desde: [developer.wordpress.com/studio](https://developer.wordpress.com/studio/) ## Novedades 2025–2026 ### Blueprints: ambientes reproducibles (octubre 2025) Los **Blueprints** son archivos JSON que definen la configuración exacta de un sitio: versión de WordPress, plugins instalados, ajustes iniciales y configuraciones de entorno. Permiten crear plantillas reutilizables o compartir configuraciones idénticas entre miembros de un equipo. ```json { "wordPressVersion": "latest", "plugins": ["woocommerce", "advanced-custom-fields"], "siteOptions": { "blogname": "Mi Tienda Local" } } ``` Studio incluye una selección de Blueprints curados para casos de uso comunes (ecommerce, multisite, desarrollo de bloques, etc.). ### Studio CLI 1.7.0: control desde la terminal (enero 2026) La versión 1.7.0 introdujo una **CLI completa** que permite controlar casi todas las funcionalidades de Studio desde la terminal, con excepción de la sincronización. Esto lo hace compatible con flujos de trabajo automatizados y herramientas como Claude Code o Cursor. Comandos principales: ```bash # Listar sitios studio sites list # Crear un sitio nuevo studio sites create --name "mi-proyecto" --php 8.5 # Iniciar/detener sitio studio sites start mi-proyecto studio sites stop mi-proyecto # Gestionar Preview Sites (compartir con clientes) studio preview create mi-proyecto studio preview list studio preview delete # Ejecutar comandos WP-CLI dentro del sitio studio wp mi-proyecto plugin list studio wp mi-proyecto core update ``` ### Xdebug: depuración paso a paso (marzo 2026) Desde el 6 de marzo de 2026, Studio incluye soporte nativo para **Xdebug**, el depurador estándar de PHP. Esto permite: - Ejecutar el código línea por línea - Inspeccionar variables en tiempo real - Establecer breakpoints desde el editor - Eliminar la dependencia de `var_dump()` para depurar Compatible con VS Code, PhpStorm y cualquier editor con soporte DAP (Debug Adapter Protocol). ### PHP 8.5 y mejoras de infraestructura Studio soporta múltiples versiones de PHP y desde marzo 2026 incluye **PHP 8.5**. La versión por defecto es PHP 8.3. Otros cambios de infraestructura recientes: - **SSL para dominios personalizados**: ambientes de desarrollo con HTTPS real - **SQLite con driver AST**: base de datos más rápida y estable - **Soporte para versiones nightly de WordPress**: ideal para contribuir al core - **PHP 8.5 disponible** para pruebas de compatibilidad anticipadas ## Compartir sitios con clientes: Preview Sites Studio genera un **enlace público temporal** hacia tu sitio local. El enlace tiene una duración configurable (por defecto 7 días) y permite que clientes o colaboradores vean el trabajo sin necesidad de un despliegue real. Desde la CLI: ```bash studio preview create nombre-sitio # Devuelve: https://preview.studio.wordpress.com/xxxxx ``` Desde la interfaz gráfica: botón "Share" en la barra superior de cada sitio. ## Sincronización con WordPress.com Studio permite hacer **push y pull** de sitios hacia WordPress.com (plan necesario). Esta funcionalidad soporta: - Pausar y reanudar operaciones de sincronización - Cancelar una transferencia en curso - Importar/exportar sitios mientras se sincroniza otro - Mejor manejo offline (eliminar sitios o cerrar sesión sin conexión) ## Editores compatibles Studio abre sitios directamente en los siguientes editores: - **VS Code** - **PhpStorm** - **Zed** (soporte añadido en 2025) - **Cursor** - **Antigravity** ## Comparación con alternativas | Herramienta | Precio | Requiere config. servidor | CLI | Blueprints | Xdebug | |------------|--------|--------------------------|-----|------------|--------| | **WordPress Studio** | Gratis | No | Sí (v1.7+) | Sí | Sí (mar 2026) | | Local (Flywheel) | Gratis / Pro | No | Sí | No | Sí | | XAMPP | Gratis | Sí | No | No | Manual | | Valet (macOS) | Gratis | Parcial | Sí | No | Manual | | DevKinsta | Gratis | No | Limitado | No | Sí | ## Preguntas Frecuentes ### ¿WordPress Studio es gratuito? Sí, es completamente gratuito y de código abierto. El repositorio está disponible en GitHub bajo licencia GPLv2. ### ¿Funciona en Windows? Sí. Desde mayo de 2024 hay versión nativa para Windows 10 y Windows 11, disponible en Microsoft Store y descarga directa desde el sitio oficial. ### ¿Necesito instalar PHP, MySQL o Apache? No. Studio incluye todo lo necesario en la propia aplicación, incluyendo PHP (configurable entre versiones) y base de datos SQLite. No se necesita instalar dependencias externas. ### ¿Puedo usar WP-CLI con Studio? Sí. Desde Studio 1.7.0, la CLI permite ejecutar comandos WP-CLI directamente contra cualquier sitio local: `studio wp nombre-sitio comando`. ### ¿Los Preview Sites son permanentes? No. Los enlaces públicos tienen duración configurable (7 días por defecto). Para alojar el sitio de forma permanente, es necesario hacer push a WordPress.com o exportarlo a otro host. ### ¿Studio soporta multisite? Sí, a través de Blueprints se puede configurar un entorno multisite desde el inicio. ## Recursos - [Documentación oficial](https://developer.wordpress.com/docs/developer-tools/studio/) - [Descarga para macOS y Windows](https://developer.wordpress.com/studio/) - [Changelog completo](https://developer.wordpress.com/docs/developer-tools/studio/changelog/) - [Repositorio en GitHub](https://github.com/Automattic/studio) --- ### Resumen del Google I/O 2024: Revelando las Innovaciones más Recientes de Google - URL: https://www.angelcruz.dev/post/resumen-google-io-2024 - Markdown: https://www.angelcruz.dev/post/resumen-google-io-2024.md - Categoría: Web - Fecha: 2024-05-14 - Excerpt: Descubre las innovaciones más recientes de Google en inteligencia artificial, Android y seguridad en el emocionante Google I/O 2024. --- title: "Resumen del Google I/O 2024: Revelando las Innovaciones más Recientes de Google" excerpt: "Descubre las innovaciones más recientes de Google en inteligencia artificial, Android y seguridad en el emocionante Google I/O 2024." date: "2024-05-14T20:09:28.000Z" category: "Web" seo_title: "Google I/O 2024: Gemini, Android 15 y chip Trillium" seo_description: "Resumen del Google I/O 2024: lanzamiento del chip Trillium, Gemini Live con contexto visual en tiempo real, Gemini Nano en Android 15, los nuevos Gem especializados y Google Gemma actualizado." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![Google IO 2024](https://io.google/2024/app/images/og-image.jpeg) Hoy, 14 de mayo, ha marcado el día en que Google ha presentado sus últimas innovaciones en el esperado evento anual, Google I/O 2024. Los espectadores de todo el mundo han tenido la oportunidad de presenciar las emocionantes novedades que la compañía tiene preparadas para este año. ## Gemini: Novedades de IA El enfoque principal del evento ha sido Gemini, la plataforma de inteligencia artificial de Google. Sundar Pichai destacó el lanzamiento de Trillium, un chip diseñado para centros de datos con mayor rendimiento y eficiencia energética, orientado a reducir el consumo en infraestructura. Gemini ahora puede interconectar aplicaciones de Google para ofrecer respuestas contextuales. La función AI Teammate, también conocida como Chip, permite interactuar con Gemini mientras se colabora con otras personas. Gemini Live, disponible a finales de este año, permite interacciones en tiempo real utilizando la cámara del teléfono para dar contexto visual a las respuestas. Además, Google presentó los "Gem", versiones de Gemini especializadas en áreas específicas, como el ejercicio físico, que pueden dar información y recomendaciones dentro de ese dominio. ## Android: Potenciado por la IA La influencia de la inteligencia artificial en Android también ha sido destacada durante el evento. Las nuevas funcionalidades, como la capacidad de realizar búsquedas utilizando gestos y realizar operaciones matemáticas simplemente dibujando en la pantalla, demuestran cómo la IA está transformando la forma en que interactuamos con nuestros teléfonos inteligentes. Gemini Nano, integrado directamente en Android 15, trae consigo una serie de nuevas características que mejoran la experiencia del usuario, incluida la capacidad de contextualizar y responder preguntas sobre documentos PDF y videos de YouTube en tiempo real. Además, Google anunció la próxima generación de Google Gemma, una colección de modelos de lenguaje abiertos que prometen mejorar la interpretación de imágenes y ofrecer un rendimiento superior a los usuarios. ## Privacidad y Seguridad: Prioridades de Google Google también se ha comprometido a garantizar la privacidad y seguridad de sus usuarios. La introducción de AI Assisted Red Teaming y SynthID demuestra el compromiso de la compañía en proteger los modelos de IA de posibles usos indebidos y garantizar la identificación de trabajos creados por IA. En resumen, el Google I/O 2024 presentó novedades de Google en inteligencia artificial, Android, privacidad y seguridad. Los anuncios principales fueron Trillium, las nuevas capacidades de Gemini y las mejoras en Android 15. --- ### Uso Eficiente de Memoria en PHP con WeakMaps - URL: https://www.angelcruz.dev/post/ahorro-memoria-php-weakmaps - Markdown: https://www.angelcruz.dev/post/ahorro-memoria-php-weakmaps.md - Categoría: PHP - Fecha: 2024-05-12 - Excerpt: Descubre cómo los WeakMaps en PHP pueden optimizar el uso de memoria, mejorando el rendimiento y escalabilidad de tus aplicaciones. --- title: "Uso Eficiente de Memoria en PHP con WeakMaps" excerpt: "Descubre cómo los WeakMaps en PHP pueden optimizar el uso de memoria, mejorando el rendimiento y escalabilidad de tus aplicaciones." date: "2024-05-12T22:59:00.000Z" category: "PHP" seo_title: "Optimizar Memoria en PHP con WeakMaps: Guía Práctica" seo_description: "WeakMap en PHP libera objetos automáticamente del garbage collector, reduciendo el uso de memoria de 2MB a menos de 1KB comparado con arrays normales en 100.000 entradas." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" --- En el desarrollo web, manejar bien la memoria puede marcar la diferencia en el rendimiento de una aplicación. En PHP, hay una estructura de datos que suele pasar desapercibida pero que resulta útil para este propósito: el WeakMap. En este artículo explico qué es un WeakMap, cómo se diferencia de un array común y en qué situaciones conviene usarlo. ## ¿Qué es un WeakMap?: Un WeakMap en PHP es una estructura de datos que nos permite asociar pares de llaves y valores, pero con una característica única: maneja las llaves como referencias débiles. Esto significa que si la única referencia a una llave está dentro del WeakMap, la llave será eliminada automáticamente de la memoria cuando ya no se utilice en ningún otro lugar del código. ## Diferencias con un Array Normal: La principal diferencia entre un WeakMap y un array normal radica en cómo manejan las referencias a las llaves. Mientras que un array normal mantiene las llaves en memoria incluso si ya no se utilizan en ningún otro lugar del código, un WeakMap elimina automáticamente las llaves de la memoria cuando ya no son necesarias, lo que ayuda a optimizar el uso de memoria en nuestra aplicación. ### Ejemplo Práctico: ```php marketing digital para e-commerce, como Relevant**. Estas agencias están dedicadas a proporcionar soluciones personalizadas y estratégicas para potenciar las ventas en línea de las pymes. Con experiencia y conocimientos profundos en el panorama del comercio electrónico, las agencias como Relevant se convierten en aliados valiosos para las pymes que buscan destacarse y alcanzar el éxito en línea. ## Alternativas más destacadas para plataformas de e-commerce con Laravel ### Laravel Spark: E-commerce con Funcionalidades de Suscripción Laravel Spark incluye gestión de inventario, carrito de compras e integración de pagos, lo que facilita la creación de una tienda virtual funcional. Al estar construido sobre Laravel, es fácil de personalizar y escalar. ### Bagisto: Flexibilidad y Adaptabilidad para tu Negocio en Línea Bagisto ofrece una amplia gama de características, incluyendo opciones de pago múltiples y gestión de inventario avanzada. Su panel de administración intuitivo y su arquitectura modular facilitan la personalización según las necesidades específicas de tu empresa. Además, Bagisto es altamente escalable, lo que te permite expandir tu tienda a medida que tu negocio crece. ### AvoRed: E-commerce Open Source sobre Laravel AvoRed es una plataforma de e-commerce de código abierto construida sobre Laravel. Permite gestionar productos, clientes y pedidos con una interfaz sencilla. Es una opción para quienes buscan una solución sin mucha configuración inicial. ### Integración de Laravel con plataformas de ecommerce existentes: Otra alternativa es utilizar Laravel para integrar con plataformas de ecommerce ya existentes como WooCommerce, Magento o Shopify. Esto te permitirá beneficiarte de las características y funcionalidades de estas plataformas populares, al tiempo que utilizas Laravel para desarrollar características personalizadas según tus necesidades específicas. Esta opción es especialmente útil si ya estás utilizando una plataforma de ecommerce y deseas ampliar su funcionalidad. Ahora que hemos explorado algunas alternativas para plataformas de e-commerce con Laravel, es importante considerar cómo puedes promocionar y hacer crecer tu negocio una vez que tu tienda esté en funcionamiento. Es importante destacar que, independientemente de la opción que elijas, es fundamental considerar otros aspectos importantes en el camino hacia el éxito de tu plataforma de ecommerce. Aquí es donde entran en juego las estrategias de marketing digital. ### Estrategias de Marketing Digital para Impulsar tu Tienda en Línea * Optimización de Motores de Búsqueda (SEO): Implementa prácticas de SEO para aumentar la visibilidad de tu tienda en línea. Investiga palabras clave relevantes y optimiza el contenido de tu sitio web para mejorar tu posición en los resultados de búsqueda. * Diseño y experiencia de usuario: Un diseño atractivo y una experiencia de usuario intuitiva son cruciales para el éxito de una plataforma de ecommerce. Asegúrate de que tu plataforma está optimizada para diferentes dispositivos y navegadores, y que ofrezca una navegación fácil y rápida. * Marketing de Contenidos: Crear contenido relevante y valioso para tu audiencia, como publicaciones de blog y guías de compra. El marketing de contenidos te ayudará a establecer tu autoridad en tu nicho y atraer a clientes potenciales a tu tienda en línea. * Redes Sociales: Utiliza las redes sociales para interactuar con tus clientes y promocionar tus productos. Publica contenido atractivo y participa en conversaciones relevantes para aumentar el conocimiento de tu marca. * Email Marketing: Construye una lista de correo electrónico y envía correos electrónicos personalizados a tus suscriptores con ofertas especiales y promociones. El email marketing es una forma efectiva de fomentar la lealtad del cliente y aumentar las ventas. * Seguridad y protección de datos: El comercio electrónico implica la manipulación de datos sensibles, como información personal y detalles de pago. Asegúrate de implementar medidas de seguridad robustas para proteger la información de tus clientes. Por último, cuando se opta por Laravel como su preferencia para las plataformas de comercio electrónico y utilizar diversas herramientas de marketing que están ahí fuera, la construcción de una tienda en línea se convierte en fácil por lo tanto conduce a su crecimiento. También es importante no olvidarse de lograr esto a través de dar a los clientes grandes experiencias junto con la elaboración de estrategias de marketing que se encargará de que su negocio acumule popularidad, así como el aumento del volumen de ventas, entre otros. --- ### Entendiendo el patrón Abstract Factory - URL: https://www.angelcruz.dev/post/patron-abstract-factory-php - Markdown: https://www.angelcruz.dev/post/patron-abstract-factory-php.md - Categoría: PHP - Fecha: 2024-04-10 - Excerpt: Mejora la arquitectura de tus proyectos PHP: domina el patrón Abstract Factory para un código más eficiente y organizado. --- title: "Entendiendo el patrón Abstract Factory" excerpt: "Mejora la arquitectura de tus proyectos PHP: domina el patrón Abstract Factory para un código más eficiente y organizado." date: "2024-04-10T02:04:00.000Z" category: "PHP" seo_title: "Patrón Abstract Factory en PHP: guía con ejemplos reales" seo_description: "Implementa el patrón Abstract Factory en PHP con interfaces y fábricas concretas. Ejemplo práctico con computadoras gamer y de edición: cuándo aplicarlo y sus ventajas de modularidad y escalabilidad." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" --- El patrón **Abstract Factory** provee una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas, y es un **patrón de diseño creacional**. Imagina que es una fábrica de fábricas; donde cada "fábrica" puede crear diferentes tipos de objetos que están interconectados. ## Ejemplo Práctico ¿Sería posible que tener tanto una "Fábrica de Computadoras para Gamers" como una "Fábrica de Computadoras para Edición de Video"? Primero, definimos interfaces para nuestros productos, es decir, los componentes de las computadoras: ```php interface Procesador { public function getVelocidad(); } interface GPU { public function getMemoria(); } ``` Luego, implementamos estas interfaces para crear productos concretos: ```php class ProcesadorGamer implements Procesador { public function getVelocidad() { return "4.5 GHz"; } } class ProcesadorEdicion implements Procesador { public function getVelocidad() { return "3.8 GHz optimizado para multitarea"; } } class GPUGamer implements GPU { public function getMemoria() { return "12 GB"; } } class GPUEdicion implements GPU { public function getMemoria() { return "8 GB, optimizado para renderizado"; } } ``` Ahora, pasamos a crear la **Abstract Factory** que define los métodos para crear estos productos: ```php interface ComputadoraFactory { public function crearProcesador(): Procesador; public function crearGPU(): GPU; } ``` A partir de este punto pasamos a implementar nuestra interfaz de la siguiente forma: ```php class ComputadoraGamerFactory implements ComputadoraFactory { public function crearProcesador(): Procesador { return new ProcesadorGamer(); } public function crearGPU(): GPU { return new GPUGamer(); } } class ComputadoraEdicionFactory implements ComputadoraFactory { public function crearProcesador(): Procesador { return new ProcesadorEdicion(); } public function crearGPU(): GPU { return new GPUEdicion(); } } ``` ## Usando el factory Para usar nuestro factory solo debemos hacerlo de forma parecida a esta: ```php function fabricarComputadora(ComputadoraFactory $factory) { $procesador = $factory->crearProcesador(); $gpu = $factory->crearGPU(); echo "Procesador: " . $procesador->getVelocidad() . "\n"; echo "GPU: " . $gpu->getMemoria() . "\n"; } // gamer pc fabricarComputadora(new ComputadoraGamerFactory()); // editar video fabricarComputadora(new ComputadoraEdicionFactory()); ``` Y nos va retornar lo siguiente: ```bash Procesador: 4.5 GHz GPU: 12 GB Procesador: 3.8 GHz optimizado para multitarea GPU: 8 GB, optimizado para renderizado ``` ## ¿Por qué es útil? - Al agrupar la creación de objetos naturalmente relacionados, se promueve la cohesión. - Utiliza interfaces en lugar de clases específicas para reducir el acoplamiento entre tu código y las clases concretas. - Permite añadir nuevas variantes de productos sin afectar el código cliente existente, siguiendo el principio de abierto/cerrado. - Por lo tanto, siempre que necesites crear familias de productos o conceptos relacionados, deberías considerar el uso del patrón Abstract Factory. Funciona como una forma elegante de mantener tu código organizado, flexible y escalable. ## ¿Cuándo aplicar el patrón Abstract Factory? Puede no ser inmediatamente evidente cuándo aplicar el patrón Abstract Factory, pero hay varias pistas y situaciones que pueden indicarte que es una buena opción considerarlo. ### Categorías de productos similares Si deseas que las "familias" de productos relacionados o dependientes entre sí sean coherentes, el patrón Abstract Factory es ideal para tu aplicación. Esto es especialmente verdadero cuando estas familias de productos están destinadas a ser utilizadas en conjunto. Pista: Posees varios objetos o productos que se utilizan en conjunto y tienen variaciones dependiendo del contexto (por ejemplo, componentes de interfaz de usuario para distintos sistemas operativos, diversos tipos de objetos para distintas configuraciones de juego, etc.). ### Necesidad de abstracción Si tu proyecto requiere trabajar con múltiples variantes de productos, pero no debe depender directamente de las clases específicas para crear esos productos. El uso de patrones te permite trabajar a un nivel de abstracción más alto, empleando interfaces para definir las acciones que puedes realizar con los productos sin detallar su implementación. Pista: Estás escribiendo código que debería poder adaptarse fácilmente a nuevas variantes de productos sin necesidad de realizar muchos cambios. ### Separación de la lógica de creación Si necesitas separar la lógica de creación de tus productos del código que los utiliza, el Abstract Factory puede ser útil. La creación de objetos se encapsula en fábricas que son implementaciones de una interfaz común, gracias a este patrón. Pista: ¿Te gustaría separar la construcción de objetos de su uso, para hacer tu código más modular y fácil de mantener? ### Inversión de Dependencia es un principio fundamental. Este principio establece que la dependencia de tu código debe ser en abstracciones, no en clases concretas. Si tu código comienza a depender en exceso de los detalles específicos de la creación de objetos, puede ser el momento adecuado para pensar en Abstract Factory. Pista: ¿Estás en la búsqueda de formas para hacer tu código más flexible y menos acoplado, al mismo tiempo que respetas los principios SOLID? ### Frecuentes cambios en familias de productos. Si prevés que las familias de productos utilizadas por tu aplicación podrían cambiar con frecuencia o que puedas necesitar agregar nuevas familias en el futuro, el patrón Abstract Factory puede ayudarte a manejar esos cambios de manera más fluida. Pista: Es necesario que tu aplicación sea escalable y pueda adaptarse a la integración de nuevas líneas de productos sin tener que realizar grandes modificaciones en el código ya existente. ## Cómo aplicarlo efectivamente Si encuentras una o varias de estas señales en tu proyecto, deberías considerar utilizar el patrón Abstract Factory. Comienza definiendo interfaces comunes para tus familias de productos y luego implementa estas interfaces en clases concretas que representen variantes específicas de los productos. Por fin, establece fábricas concretas que engloben la fabricación de estas variaciones de productos. > Ten en cuenta que el propósito principal de este patrón es aumentar la modularidad y flexibilidad de tu código, haciendo que sea más sencillo extenderlo y mantenerlo a lo largo del tiempo. --- ### Laravel 11: Guía práctica de Inicio - URL: https://www.angelcruz.dev/post/laravel-11-guia-inicio - Markdown: https://www.angelcruz.dev/post/laravel-11-guia-inicio.md - Categoría: Laravel - Fecha: 2024-03-12 - Excerpt: Descubre lo nuevo que llega hoy a Laravel en su versión 11. Una estructura más compacta y simple, no estás obligado a adoptarlo de inmediato; todas las aplicaciones existentes construidas sobre la estructura de Laravel 10 seguirán funcionando sin problemas. --- title: "Laravel 11: Guía práctica de Inicio" excerpt: "Descubre lo nuevo que llega hoy a Laravel en su versión 11. Una estructura más compacta y simple, no estás obligado a adoptarlo de inmediato; todas las aplicaciones existentes construidas sobre la estructura de Laravel 10 seguirán funcionando sin problemas." date: "2024-03-12T05:43:00.000Z" category: "Laravel" seo_title: "Laravel 11: guía de inicio rápido con Breeze y SQLite" seo_description: "Guía práctica para comenzar un proyecto en Laravel 11. Nueva estructura de directorios, instalación con Breeze, configuración de middlewares en bootstrap/app.php y SQLite por defecto." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Hoy es el dia de lanzamiento de Laravel 11 y con este articulo repasaremos lo basico que necesitas para empezar un proyecto nuevo. ## Instalación Teniendo el instalador de Laravel el cual se instala previamente de esta forma: ```bash composer global require laravel/installer ``` solo basta correr el siguiente comando para inicializar nuestro nuevo proyecto: ```bash laravel new ``` Despues de eso nos encontraremos con el siguiente prompt: ```bash _ _ | | | | | | __ _ _ __ __ ___ _____| | | | / _` | '__/ _` \ \ / / _ \ | | |___| (_| | | | (_| |\ V / __/ | |______\__,_|_| \__,_| \_/ \___|_| ┌ Would you like to install a starter kit? ────────────────────┐ │ › ● No starter kit │ │ ○ Laravel Breeze │ │ ○ Laravel Jetstream │ └──────────────────────────────────────────────────────────────┘ ``` Aca podremos seleccionar el starter kit que usaremos en nuestro proyecto, para este ejemplo seleccionaré breeze. Al seleccionarlo nos preguntaria cual stack vamos a usar: ```bash ┌ Which Breeze stack would you like to install? ───────────────┐ │ › ● Blade with Alpine │ │ │ ○ Livewire (Volt Class API) with Alpine │ │ │ ○ Livewire (Volt Functional API) with Alpine │ │ │ ○ React with Inertia │ │ │ ○ Vue with Inertia │ │ │ ○ API only ┃ │ └──────────────────────────────────────────────────────────────┘ ``` ```blade +parse Por simplicidad seleccionaré `Blade with Alpine` ``` Luego de esa selección otro promt más preguntando si queremos soporte a `dark mode` ```bash ┌ Would you like dark mode support? ───────────────────────────┐ │ ○ Yes / ● No │ └──────────────────────────────────────────────────────────────┘ ``` Luego de la instalación de los paquetes y todo eso nos preguntaría el motor a base de datos a usar, selecionaré `SQLite` ```bash ┌ Which database will your application use? ───────────────────┐ │ ○ MySQL │ │ ○ MariaDB │ │ ○ PostgreSQL │ │ › ● SQLite │ │ ○ SQL Server │ └──────────────────────────────────────────────────────────────┘ ``` Luego esperamos un momento a que termine todo el proceso de instalación e iremos a ver a nuestra nueva página de inicio: ![página de inicio de Laravel 11](https://cdn.angelcruz.dev/article/kpxLfv7e45bDroEibkOC05qrU0SpJvzE3BddaIyD.png) ## Estructura de directorios Laravel 11 se caracteriza por tener una estructura más reducida a nivel de archivos como se puede ver aquí en este ejemplo: ```bash ├── app │ ├── Http │ │ └── Controllers │ │ └── Controller.php │ ├── Models │ │ └── User.php │ └── Providers │ └── AppServiceProvider.php ``` ## Una mejor organizacion para los middlewares, servicios, excepciones Ahora, dentro del directorio `bootstrap/app.php` se podrá configurar los middlewares que usaremos dentro de nuestra aplicación ya que tendrá una estructura de esta forma: ```php withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ``` ## Un simple AppServiceProvider Laravel 11 consolida múltiples `Service Providers` en uno solo, reduciendo el código repetitivo y mejorando la eficiencia de construcción de nuestra aplicación. ## Sistema de rutas más simples El directorio `routes` ahora cuenta solamente con el siguiente par de archivos: ```bash ├── routes │ ├── console.php │ └── web.php ``` lo que hace opcional la instalacion / publicación del archivo `api.php` en el directorio `routes` por medio del comando: ```bash php artisan install:api ``` El cual al momento de usarlo procederá a la instalación de `laravel/sanctum` y tambien va a proceder a preguntar si deseas correr la migración pendiente: ```bash One new database migration has been published. Would you like to run all pending database migrations? (yes/no) [yes]: ``` Al igual que el archivo `channels.php` luego de usar el comando: ```bash php artisan install:broadcasting ``` Realizaria la instalación del archivo de configuración y preguntaría que si quieres instalar `Laravel Reverb` ```bash INFO Published 'broadcasting' configuration file. INFO Published 'channels' route file. ┌ Would you like to install Laravel Reverb? ───────────────────┐ │ ● Yes / ○ No │ └──────────────────────────────────────────────────────────────┘ ``` ## Controladores Ahora es mucho más simple incluir lo que necesitemos en nuestros controladores. En comparación a Laravel 10, ahora nuestrio archivo `Controller` es una clase abstracta lo que nos permite incluir lo que quisieramos en cada uno de nuestros controladores de la aplicación. ```php Este archivo NO debe ser modificado manualmente. De lo contrario, podrías romper fácilmente tu aplicación. ``` ### ¿Para qué sirve el archivo Composer.lock? El archivo **composer.lock** cumple varios propósitos importantes en el desarrollo de aplicaciones **PHP**: * Reproducibilidad de las Dependencias: Una de las principales ventajas del archivo **composer.lock** es que garantiza que todas las personas que trabajan en un proyecto utilicen exactamente las mismas versiones de las dependencias. Esto ayuda a evitar problemas de compatibilidad y asegura que el código se comporte de la misma manera en todos los entornos. * Consistencia en el Despliegue: Al incluir el archivo **composer.lock** en el repositorio del proyecto, se asegura de que las mismas versiones de las dependencias se instalen en todos los entornos, incluidos los servidores de producción. Esto reduce la posibilidad de errores inesperados causados por diferencias en las versiones de las dependencias entre entornos. * Mejora del Tiempo de Desarrollo: Al fijar las versiones de las dependencias en el archivo **composer.lock**, **Composer** puede evitar descargar versiones actualizadas de las dependencias cada vez que se ejecuta el comando composer install. Esto puede ahorrar tiempo de desarrollo y reducir la posibilidad de problemas causados por actualizaciones inesperadas de las dependencias. * Seguridad del Proyecto: El archivo **composer.lock** también puede ayudar a mejorar la seguridad del proyecto al garantizar que se utilicen versiones actualizadas y seguras de las dependencias. Al fijar las versiones en el archivo composer.lock, los desarrolladores pueden controlar cuidadosamente qué versiones de las dependencias se utilizan en el proyecto y asegurarse de que no haya vulnerabilidades conocidas. ```blade +parse Este archivo debe ser enviado al repositorio para garantizar que tus compañeros de equipo y los servidores de staging / producción tengan exactamente las mismas versiones de paquetes. De lo contrario, podrías encontrarte en una situación donde tu aplicación funcione localmente, pero no en el servidor de producción o de tus compañeros. ``` En resumen, el archivo **composer.lock** es una parte fundamental del ecosistema de desarrollo de aplicaciones **PHP**. Actúa como un registro de las versiones exactas de todas las dependencias de un proyecto y garantiza la reproducibilidad, la consistencia y la seguridad del proyecto. Al comprender la importancia del archivo **composer.lock** y utilizarlo correctamente en los proyectos PHP, los desarrolladores pueden asegurarse de que sus aplicaciones sean estables, seguras y fáciles de mantener a lo largo del tiempo. --- ### Descubre las novedades de Laravel 11 - URL: https://www.angelcruz.dev/post/laravel-11-novedades - Markdown: https://www.angelcruz.dev/post/laravel-11-novedades.md - Categoría: Laravel - Fecha: 2024-02-26 - Excerpt: Descubre las emocionantes mejoras de Laravel 11 para construir aplicaciones web avanzadas y eficientes. ¡El futuro del desarrollo web está aquí! --- title: "Descubre las novedades de Laravel 11" excerpt: "Descubre las emocionantes mejoras de Laravel 11 para construir aplicaciones web avanzadas y eficientes. ¡El futuro del desarrollo web está aquí!" date: "2024-02-26T22:38:15.000Z" category: "Laravel" seo_title: "Novedades de Laravel 11: estructura simplificada y Dumpable" seo_description: "Laravel 11 requiere PHP 8.2 mínimo, simplifica la estructura eliminando Service Providers redundantes, introduce el Trait Dumpable y cambia Model Casts a definiciones de método." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Como desarrollador web, siempre estoy atento a las novedades en el mundo de la programación. Y cuando se trata de construir aplicaciones web seguras y eficientes, Laravel siempre ha sido mi elección. Con el lanzamiento de **Laravel 11**, comparto mi experiencia personal explorando las características de esta versión. ## Fecha de Lanzamiento de Laravel 11 **Laravel 11** fue anunciado para el 6 de febrero de 2024. Según la política de soporte de Laravel, los lanzamientos principales suelen llegar anualmente durante el primer trimestre, y esta vez no fue la excepción. Antes de migrar, conviene evaluar si tu aplicación actual requiere una actualización inmediata. Aunque actualmente estamos a finales de Febrero aun hay algunas cosas que Taylor está terminando de pulir para ofrecer como siempre, una de las mejores experiencias en cada release. ```blade +parse ``` ## Explorando las Novedades de Laravel 11 Estas son algunas de las características y cambios más relevantes de **Laravel 11**: ### Fin del Soporte para PHP 8.1: **Laravel 11** descontinúa el soporte para PHP 8.1. PHP 8.2 y 8.3 son ahora el mínimo requerido. ![php8.2](https://www.php.net/images/php8/php_8_2_released.png) Este cambio fue documentado en este [PR](https://github.com/laravel/framework/pull/45526) ### Estructura de Aplicación Simplificada: **Laravel 11** presenta una estructura de aplicación más simplificada, eliminando el código redundante y facilitando el proceso de desarrollo. Desde la eliminación automática de políticas y eventos hasta la integración de funcionalidades personalizadas de Artisan, Laravel 11 mejora la eficiencia y la mantenibilidad del código en todos los aspectos. ```bash app ├── Http │ └── Controllers │ └── Controller.php ├── Models │ └── User.php └── Providers └── AppServiceProvider.php bootstrap ├── app.php ├── cache │ ├── packages.php │ └── services.php └── providers.php ``` #### Cambios específicos: * En `AuthServiceProvider`, el framework descubre y elimina automáticamente las '$policies'. * Ya no necesitas `SendEmailVerificationNotification` en `EventServiceProvider`, ya que el `EventServiceProvider` base lo registra. Además, notarás que Laravel ahora habilita la autodetección de eventos de forma predeterminada. * `BroadcastServiceProvider` ya no es necesario y, como resultado, se ha eliminado. * El framework ya no carga automáticamente el archivo `routes/channels.php`. * `RedirectIfAuthenticated` es facilitado por la funcionalidad central del framework. * El middleware `Authenticate` ya no invoca el método `redirectTo()` para rutas JSON, eliminando la necesidad de verificaciones ternarias redundantes. Los demás cambios pueden verse en este [PR realizado por Taylor ](https://github.com/laravel/laravel/pull/6172) ### Introducción del Trait Dumpable: **Laravel 11** agrega el Trait `Dumpable`, que permite integrar funciones de depuración directamente en las clases. Simplifica el debugging sin necesidad de helpers externos. ### Evolución de los Model Casts: En **Laravel 11**, los `Model Casts` pasan de ser una propiedad a una definición de método. Esto mejora la flexibilidad y facilita agregar lógica dentro de los casts. ### Gestión de Configuraciones: **Laravel 11** centraliza las opciones de configuración en el archivo `.env` por defecto, eliminando la necesidad de archivos de configuración separados para la mayoría de los casos. El comando `config:publish` permite publicar archivos de configuración específicos cuando se necesitan. ### Resumen de cambios en Laravel 11 Laravel 11 trae una estructura de aplicación más reducida, elimina archivos de configuración redundantes, introduce el Trait Dumpable y cambia los Model Casts a definiciones de método. Son cambios incrementales orientados a simplificar el código base de proyectos nuevos. --- ### Cómo Optimizar Query Scopes en Laravel para Autocompletado IDE - URL: https://www.angelcruz.dev/post/optimizar-query-scopes-laravel-autocompletado-ide - Markdown: https://www.angelcruz.dev/post/optimizar-query-scopes-laravel-autocompletado-ide.md - Categoría: Laravel - Fecha: 2024-02-19 - Excerpt: Optimiza tus query scopes en Laravel para un autocompletado más amigable en tu IDE favorito. Aprende cómo configurarlos adecuadamente y maximiza la eficiencia de tus consultas SQL. Simplifica tu flujo de trabajo y mejora tu productividad con este tutorial. --- title: "Cómo Optimizar Query Scopes en Laravel para Autocompletado IDE" excerpt: "Optimiza tus query scopes en Laravel para un autocompletado más amigable en tu IDE favorito. Aprende cómo configurarlos adecuadamente y maximiza la eficiencia de tus consultas SQL. Simplifica tu flujo de trabajo y mejora tu productividad con este tutorial." date: "2024-02-19T01:38:00.000Z" category: "Laravel" seo_title: "Query Scopes en Laravel con autocompletado IDE: UserBuilder" seo_description: "Mejora el autocompletado de query scopes en Laravel con el patrón UserBuilder. Sobreescribe newEloquentBuilder() y query() para que tu IDE sugiera todos los scopes del modelo correctamente." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Como sabemos, una de las características más poderosas de Laravel son los query scopes ya que son fáciles de usar, los query scopes facilita la creación de consultas SQL complejas que en algunos casos se pueden volver a aplicar la misma condición a otros modelos dentro de tu aplicación. [En un artículo anterior](/post/como-se-usan-los-query-scopes) hablabamos que los query scopes son una alternativa para optimizar nuestro código cuando necesitamos hacer condiciones específicas en nuestras consultas. En ese artículo teniamos el siguiente ejemplo: ```php where('active', 1); } } ``` Ahora vamos a realizar una serie de cambios para permitir que nuestro IDE pueda sugerirnos todos los scopes que tengamos para nuestro modelo `User` Una de las cosas que debemos tener en cuenta es que cada vez que hagamos una consulta usando `Eloquent` estamos pasando por el propio `Query Builder` por lo que siempre debemos pasar como parámetro la `query` que se esta ejecutando para poder encadenar todos los métodos adicionales que vayamos a aplicar en la consulta. Ejemplo: Si tenemos esta ruta ```php Route::get('/users', function () { $users = User::query(); dd($users); }); ``` El resultado de ese `dd()` sería el siguiente: ```html Illuminate\Database\Eloquent\Builder ``` Por lo que para poder hacer todo lo que dije arriba debemos hacer lo siguiente en nuestro modelo `User`: ```php where('active', 1) } // cualquier otro scope que tengamos definido } ``` Y ya para finalizar, en nuestro modelo `User` debemos hacer un pequeño cambio más: ```php cómo crear un ChatBot en WhatsApp con Kommo. En este artículo repasamos qué es Kommo CRM y qué incluye su integración con WhatsApp. ## ¿Qué es un CRM? CRM se traduce como «Gestión de Relaciones con el Cliente». Es una estrategia de negocio y un conjunto de tecnologías que las empresas utilizan para administrar y analizar las interacciones y relaciones con sus clientes y clientes potenciales. El objetivo principal del CRM es mejorar la satisfacción del cliente, retener clientes existentes y adquirir nuevos clientes de manera eficiente. ### Algunas de las funciones clave de un sistema de CRM incluyen: - Gestión de contactos: Almacenar y organizar la información de los clientes de manera centralizada. - Automatización de ventas: Seguimiento de oportunidades de venta, cotizaciones, pedidos y pronósticos de ventas. - Atención al cliente: Seguimiento de solicitudes de servicio, reclamaciones y consultas de los clientes. - Marketing: Segmentación de clientes, campañas de marketing personalizadas y seguimiento de resultados. - Analítica: Generación de informes y análisis para tomar decisiones informadas sobre estrategias de negocios. ### ¿Qué es Kommo CRM? Kommo CRM es una herramienta de CRM conversacional que combina los métodos de gestión de clientes en una solución unificada. Permite a las empresas la agilización de sus procesos de gestión de clientes, entre otras soluciones que ofrecen una excelente ayuda a las empresas. Una de las ventajas de esta herramienta es la integración de la plataforma de mensajería instantánea WhatsApp, debido a esto, CRM WhatsApp de Kommo permite a las empresas utilizar WhatsApp para una gestión más eficiente de los clientes. ### Ventajas del uso de CRM WhatsApp de Kommo CRM Kommo CRM es una plataforma de CRM conversacional que centraliza la gestión de clientes en una sola herramienta. Permite a todo tipo de empresas y organizaciones aprovechar de forma eficiente las ventas basadas en mensajería, con el fin de conectarse con clientes potenciales en una variedad de canales de comunicación. ### Chatbots de WhatsApp Kommo CRM incluye chatbots de WhatsApp configurables para responder preguntas frecuentes, asignar tareas y recopilar información. Usan procesamiento de lenguaje natural para interpretar las preguntas de los clientes y devolver respuestas estructuradas. Adicional a todo lo mencionado anteriormente, este tipo de asistentes pueden contribuir a automatizar actividades repetitivas, como la asignación de solicitudes, la actualización de los datos de los clientes y la recopilación de información para su análisis. Al aprovechar estas herramientas automatizadas, las organizaciones pueden elevar la calidad del servicio al cliente, reducir los tiempos de respuesta y optimizar la eficiencia de su equipo de trabajo. ## Características de CRM WhatsApp de Kommo CRM ### Bandeja de entrada unificada para las conversaciones de WhatsApp La bandeja de entrada integrada de Kommo CRM centraliza todas las conversaciones de WhatsApp en una sola interfaz. Funciona con múltiples números de WhatsApp y distintos canales al mismo tiempo. Esto implica que las empresas pueden acceder y revisar todas las interacciones con sus clientes, realizando un seguimiento sin complicaciones de las conversaciones, sin importar el canal o número utilizado. Esta aproximación unificada garantiza que ninguna comunicación quede en el olvido, lo que habilita a las empresas para brindar experiencias fluidas y coherentes a su clientela. ### Plantillas personalizadas para WhatsApp Kommo CRM ofrece la versatilidad necesaria para crear y emplear plantillas personalizadas de mensajes en WhatsApp que incluyen archivos multimedia y botones. Estas plantillas tienen la capacidad de combinar texto con elementos adjuntos como imágenes, videos y audios, lo que eleva el atractivo y la interactividad de las interacciones con los clientes. La disponibilidad de plantillas predefinidas permite a las empresas economizar tiempo y garantizar la uniformidad en los mensajes en todos los puntos de contacto con sus clientes. Las plantillas admiten campos variables que se completan automáticamente con la información del perfil de cada cliente. Esto permite personalizar cada mensaje sin trabajo manual extra. --- ### Aprende Laravel: Vistas & Layouts - URL: https://www.angelcruz.dev/post/aprende-laravel-vistas-layouts - Markdown: https://www.angelcruz.dev/post/aprende-laravel-vistas-layouts.md - Categoría: Laravel - Fecha: 2023-06-03 - Excerpt: Las vistas ofrecen una presentación visual de los resultados (una pantalla de nuestro sitio web) al usuario, quien podrá interactuar con ella. --- title: "Aprende Laravel: Vistas & Layouts" excerpt: "Las vistas ofrecen una presentación visual de los resultados (una pantalla de nuestro sitio web) al usuario, quien podrá interactuar con ella." date: "2023-06-03T23:05:18.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Blade en Laravel 13: Vistas, Layouts y Componentes (Guía Completa)" seo_description: "Aprende a usar Blade, el motor de plantillas de Laravel: vistas, layouts, components, directivas y el stack TALL. Guía paso a paso." keywords: - laravel - blade laravel - vistas laravel - layouts blade - laravel español learning_path: series: "laravel-fundamentals" order: 3 total: 6 prev_slug: "aprende-laravel-rutas" next_slug: "aprende-laravel-controllers" --- ![Laravel Logo](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) Esta división nos permite separar la parte de presentación de los resultados de la lógica (controladores) y la base de datos (modelos). Por ende, no tendremos que realizar ningún tipo de consulta o procesamiento de datos, sino recibir los datos y prepararlos para mostrarlos en formato HTML. ### Definir vistas Las vistas se almacenan en la carpeta `resources/views` como ficheros PHP, y por lo tanto tendrán la extensión `.blade.php`. Contendrán el código HTML de nuestro sitio web, mezclado con los assets (CSS, imágenes, Javascripts, etc. que estarán almacenados en la carpeta public) y algo de código PHP o código Blade que ya hablaremos de esto. ### Cómo usar una vista Por el momento, vamos a llamar a las vistas desde una ruta tal cual lo hicimos en el [articulo anterior](/post/aprende-laravel-rutas) de esta forma: ```php Route::get('/', function () { return view('welcome'); }); ``` O de esta otra forma: ```php Route::view('/', 'welcome'); ``` Aunque la forma correcta sería llamar a las vistas desde un controlador de esta manera: ```php También si quisiéramos podríamos renombrar la vista `welcome.blade.php` a `index.blade.php` ### Pasar datos a una vista Pasar datos a una vista es muy fácil y se puede hacer de varias formas, dependiendo de cómo estemos llamando a esa vista. Por ejemplo, [anteriormente](/post/aprende-laravel-rutas) habíamos hablado de cómo a una ruta se le puede pasar un parámetro y recibirlo posteriormente de la siguiente forma: ```php Route::get('/{user_id}', function ($user_id) { return view('welcome'); // [tl! remove] return view('welcome', compact('user_id')); // [tl! add] }); ``` Y en nuestra vista `index` tendríamos algo como esto: ```html

El id recibido es {{ $user_id }}

``` > Cuando lleguemos al artículo sobre los controladores, retomaremos la explicación sobre cómo pasar datos a una vista. ### Blade Laravel utiliza Blade como motor de plantillas en las vistas. Blade es una poderosa herramienta que nos permite definir y estructurar las vistas de una manera más eficiente y legible. En Blade, el código dentro de una vista se inicia con los símbolos `@` o `{{ }}`. Estos símbolos indican a Blade que se debe procesar y mostrar el contenido correspondiente al renderizar la vista. El símbolo `@` se utiliza para incluir directivas de Blade, que son instrucciones especiales que nos permiten realizar diferentes acciones dentro de la vista, como condicionales, bucles y más. Por otro lado, el símbolo `{{ }}` se utiliza para imprimir variables en la vista. Esto nos permite mostrar dinámicamente contenido proveniente de nuestra lógica de aplicación. Es importante destacar que Blade no añade sobrecarga de procesamiento en cada solicitud, ya que todas las vistas son preprocesadas y cacheadas, lo que mejora el rendimiento de la aplicación. Esto significa que las vistas son compiladas una sola vez y se almacenan en caché para su uso posterior, a menos que haya cambios en los archivos de la vista. Además de la eficiencia, Blade nos brinda utilidades que nos ayudan en el diseño y modularización de las vistas. Podemos utilizar directivas, layouts, componentes y más para crear vistas más estructuradas y reutilizables. Para mayor información sobre cómo usar Blade te invito a leer la [documentación oficial](https://laravel.com/docs/13.x/blade). ### Tallstack ![Tallstack](https://cdn.angelcruz.dev/images/tall-stack1.jpeg) TALL stack es una combinación de tecnologías que facilita el desarrollo de aplicaciones web con Laravel. Combina: - [Tailwind CSS](https://tailwindcss.com/) - [Alpine.js](https://alpinejs.dev/) - [Laravel](https://laravel.com/) - [Livewire](https://laravel-livewire.com/) [Tallstack](https://tallstack.dev/) es usado principalmente en aplicaciones de backend o paneles administrativos. Actualmente Laravel viene con plantillas o vistas construidas con Tailwind CSS y hace uso de Alpine.js para ejecutar ciertas acciones que requieren el uso de javascript. El uso de Livewire (actualmente en su versión 4) sirve para dar cierta reactividad a la aplicación sin tener toda la complejidad de React, VueJs o Angular. ### Siguiente Paso Ahora que sabes crear vistas con Blade, en el próximo artículo aprenderemos sobre [Controllers](/post/aprende-laravel-controllers), donde organizaremos la lógica de nuestra aplicación de forma profesional. ## Preguntas Frecuentes ### ¿Qué es Blade en Laravel? Blade es el motor de plantillas de Laravel que permite escribir código más limpio y eficiente en las vistas. Utiliza directivas especiales con `@` y `{{ }}` para incluir lógica y variables. Lo mejor es que Blade precompila las vistas y las cachea, por lo que no añade sobrecarga de rendimiento. ### ¿Cómo paso datos a una vista? Usa la función `compact()` en tu ruta o controlador: `return view('welcome', compact('user_id'))`. También puedes pasar un array: `return view('welcome', ['user_id' => $id])`. En la vista accedes a los datos con `{{ $user_id }}`. ### ¿Dónde se guardan las vistas en Laravel? Las vistas se almacenan en la carpeta `resources/views` como archivos `.blade.php`. Puedes organizarlas en subcarpetas - por ejemplo, `resources/views/home/welcome.blade.php` se llama con `view('home.welcome')`. ### ¿Qué diferencia hay entre {{ }} y {!! !!}? `{{ $variable }}` **escapa** el HTML automáticamente (previene XSS), mientras que `{!! $html !!}` **no escapa** y muestra HTML raw. Usa `{{ }}` por defecto por seguridad, y `{!! !!}` solo cuando necesites renderizar HTML confiable. ### ¿Qué es TALL stack? TALL stack es una combinación de **T**ailwind CSS + **A**lpine.js + **L**aravel + **L**ivewire. Es ideal para aplicaciones backend/paneles administrativos que necesitan reactividad sin la complejidad de React o Vue. Laravel 13 usa Tailwind y Alpine por defecto. ### Video de la lección Ver video tutorial: [Aprende Laravel - Vistas y Layouts](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) --- ### Aprende Laravel: Rutas - URL: https://www.angelcruz.dev/post/aprende-laravel-rutas - Markdown: https://www.angelcruz.dev/post/aprende-laravel-rutas.md - Categoría: Laravel - Fecha: 2023-05-30 - Excerpt: Este es el segundo artículo de seis relacionado a como usar laravel por primera vez, en este artículo vamos a conocer lo básico del sistema de rutas de Laravel --- title: "Aprende Laravel: Rutas" excerpt: "Este es el segundo artículo de seis relacionado a como usar laravel por primera vez, en este artículo vamos a conocer lo básico del sistema de rutas de Laravel" date: "2023-05-30T22:40:37.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Rutas en Laravel 13: Routing, Parámetros, Grupos y Route Model Binding" seo_description: "Aprende el sistema de routing de Laravel: rutas básicas, parámetros, nombres, grupos y route model binding. Guía paso a paso en español." keywords: - laravel - rutas laravel - routing laravel - route parameters - laravel español learning_path: series: "laravel-fundamentals" order: 2 total: 6 prev_slug: "aprende-laravel-instalacion-setup" next_slug: "aprende-laravel-vistas-layouts" --- ![Laravel Logo](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) Enrutamiento se trata de agregar rutas que permiten a las personas acceder a tu aplicación web. Sí, es tan simple como eso. Llamamos a esas rutas "routes" en inglés. ## Rutas en Laravel Laravel tiene una carpeta dedicada llamada "`routes`", que alberga todos los diferentes tipos de rutas que existen en Laravel. Si revisas la carpeta de rutas de nuestra aplicación, verás 4 archivos de rutas. Primero, permíteme explicar esos archivos. - `api.php`: Aquí es donde puedes definir las rutas de la API. Cada ruta definida aquí se prefija automáticamente con "`api/`". Las rutas definidas aquí son sin estado (stateless). > Sin estado significa que no hay registro de la solicitud anterior y cada solicitud debe ser manejada únicamente en base a la información que la acompaña. - `channels.php`: Aquí es donde puedes definir canales de transmisión para diferentes eventos en nuestra aplicación. - `console.php`: Aquí es donde puedes definir tus propios comandos de Artisan. Todos los comandos definidos aquí se basan en closures. > Básicamente, una closure en PHP es una función que se puede crear sin un nombre especificado, es decir, una función anónima. - `web.php`: Aquí es donde puedes definir las rutas que tienen estado. Las rutas definidas aquí se basan en sesiones. Nos centraremos en este archivo durante toda esta serie. ## Route Facade El *facade* `route` es quien realiza toda la magia dentro de los archivos `api.php` y `web.php`. ![](https://media.tenor.com/DNCBqbguizsAAAAC/magic.gif) Todo el código que hace que el enrutamiento funcione se encuentra en el directorio `illuminate/Routing/`, pero accedemos a él a través de la fachada / facade `Illuminate\Support\Facades\Route`. ## Ejemplos de rutas Casi todos los métodos de ruta aceptan dos parámetros: la ruta y un callback (excepto algunos de ellos). Tenemos una ruta home dentro del archivo `web.php` que es parecida a esta: ```php Route::get('/', function () { return view('welcome'); }); ``` Como pueden ver, esta ruta en particular cumple con el enunciado anterior "una ruta y un callback". Esta es la ruta predeterminada definida en el archivo web.php. Esta ruta le indica a Laravel que muestre el archivo `resources/views/welcome.blade.php` cuando alguien accede a nuestra URL base. El segundo parámetro es un callback, en este caso una función anónima. Esta ruta también podemos escribirla de la siguiente forma: ```php Route::view('/', 'welcome'); ``` Para este ejemplo solo necesitamos dos cosas, la ruta y la vista que vamos a mostrar. También podemos pasar el nombre de un controlador y una función como callback que se encargará de la solicitud de esta forma: ```php Route::get('/', 'HomeController@index'); ``` En este ejemplo, la petición que se está ejecutando va a ser resuelta por el método `index` del controlador `HomeController` > Hay diferentes formas de resolver una petición desde un controlador y eso lo veremos en un artículo específico de Controladores ## Los parámetros de ruta Pasando variables a través de la URL es algo común entre los desarrolladores y la forma más fácil de hacerlo es adjuntando una cadena de consulta a la URL, como esta: `http://localhost?user_id=1` En Laravel, puedes adjuntar una variable a la URL sin la cadena de consulta tradicional. Definir una ruta con parámetros es fácil. El parámetro que forma parte de una ruta debe estar dentro de un par de llaves y debe ser pasado como parámetro a la función de esta forma: ```php Route::get('/{user_id}', function ($user_id) { return view('welcome'); }); ``` También puedes usar **Route Model Binding** para cargar automáticamente models: ```php Route::get('/post/{post}', function (Post $post) { return view('post.show', compact('post')); }); ``` Laravel automáticamente busca el Post por ID. Si no existe, retorna 404. ### Siguiente Paso En el próximo artículo aprenderemos sobre [Vistas y Layouts en Laravel](/post/aprende-laravel-vistas-layouts), donde veremos el motor de plantillas Blade. ## Preguntas Frecuentes ### ¿Qué son las rutas en Laravel? Las rutas son URLs que permiten acceder a tu aplicación web. Actúan como un sistema de enrutamiento que conecta URLs específicas con código que genera respuestas (vistas, JSON, redirecciones, etc.). En Laravel se definen principalmente en `routes/web.php`. ### ¿Cuál es la diferencia entre web.php y api.php? Las rutas en **web.php** tienen estado (basadas en sesiones) y están pensadas para aplicaciones web tradicionales. Las rutas en **api.php** son sin estado (stateless), se prefijan automáticamente con `/api/`, y están diseñadas para APIs RESTful donde cada solicitud debe manejarse únicamente con la información que la acompaña. ### ¿Cómo paso parámetros a una ruta? Usa llaves en la definición de la ruta: `Route::get('/{user_id}', function ($user_id) { ... })`. Los parámetros entre llaves se pasan automáticamente como argumentos a la función callback. ### ¿Qué es Route Model Binding? Es una característica de Laravel que carga automáticamente modelos Eloquent basándose en parámetros de ruta. Por ejemplo: `Route::get('/post/{post}', function (Post $post) { ... })` busca automáticamente el Post por ID y retorna 404 si no existe. ### ¿Puedo usar controladores en lugar de closures? Sí, y es la forma recomendada para aplicaciones profesionales. En lugar de `Route::get('/', function() { ... })`, usa `Route::get('/', [HomeController::class, 'index'])`. Los controladores organizan mejor tu código y facilitan el testing. ### Video de la lección Ver video tutorial: [Aprende Laravel - Rutas](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) --- ### Aprende Laravel: Instalación & Setup - URL: https://www.angelcruz.dev/post/aprende-laravel-instalacion-setup - Markdown: https://www.angelcruz.dev/post/aprende-laravel-instalacion-setup.md - Categoría: Laravel - Fecha: 2023-05-22 - Excerpt: Aprende Laravel desde cero: instalación y setup paso a paso (Parte 1/6). Conocimiento básico necesario para dominar este framework PHP moderno. --- title: "Aprende Laravel: Instalación & Setup" excerpt: "Aprende Laravel desde cero: instalación y setup paso a paso (Parte 1/6). Conocimiento básico necesario para dominar este framework PHP moderno." date: "2023-05-22T13:06:03.000Z" lastModified: "2026-03-29T00:00:00.000Z" category: "Laravel" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" seo_title: "Cómo Instalar Laravel 13 desde Cero: Guía Paso a Paso (2026)" seo_description: "Aprende a instalar Laravel 13 paso a paso: requisitos, entornos de desarrollo (Herd, Sail, Laragon), Starter Kits y configuración básica." keywords: - laravel - instalación laravel - setup laravel - laravel herd - laravel español learning_path: series: "laravel-fundamentals" order: 1 total: 6 next_slug: "aprende-laravel-rutas" --- ![Laravel Logo](https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/laravel-banner.svg) Antes de iniciar con Laravel debes cumplir con ciertos requerimientos básicos: ### Requisitos del Sistema Laravel 13 requiere: - **PHP 8.3 o superior** (recomendado PHP 8.4) - Composer (gestor de dependencias) - Extensiones PHP: BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, XML ### Entornos de Desarrollo Laravel ofrece varias opciones para configurar tu entorno de desarrollo: **Laravel Herd (Recomendado para Windows/Mac)** Laravel Herd es la forma más rápida de comenzar con Laravel. Es una aplicación nativa que incluye PHP, Nginx y todo lo necesario. - Descarga: [https://herd.laravel.com](https://herd.laravel.com) - Instalación en 1 click - Cambia entre versiones de PHP fácilmente - Ideal para principiantes **Laravel Sail (Docker)** Si prefieres Docker o usas Linux, Laravel Sail es perfecto: - Entorno Docker preconfigurado - Incluye MySQL, Redis, Mailpit, etc. - Consistente entre desarrollo y producción **Laragon (Windows)** Si has sido desarrollador de PHP, probablemente ya conozcas XAMPP o WAMP. Laragon es mejor: incluye MySQL, PHP, Memcached, Redis, Apache y Nginx en una interfaz sencilla. **Valet (Mac/Linux)** Para usuarios de Mac/Linux que quieren un entorno minimalista sin Docker. ### Composer Para instalar Laravel necesitamos de composer. Composer es una herramienta de gestión de dependencias. Usar Composer es muy sencillo. Para instalar composer debes seguir [este enlace](https://getcomposer.org/doc/00-intro.md) y cumplir con los requerimientos. ### Creando un proyecto Laravel Puedes instalar Laravel mediante el instalador de Laravel o mediante el comando `create-project` de Composer. Abre tu terminal, muévete al directorio de tu proyecto ejecutando `cd directorio_del_proyecto`. > Puedes ubicar tu proyecto de Laravel donde desees. Para realizar la instalación con el comando `create-project` debes hacer lo siguiente: ```bash composer create-project laravel/laravel nombre_proyecto ``` O usando el instalador de Laravel (más rápido): ```bash laravel new nombre_proyecto ``` ### Starter Kits (Nuevo en Laravel 13) Laravel 13 renovó los Starter Kits con tres opciones **standalone** modernas que incluyen todo desde el primer momento: **React Starter Kit** (Recomendado para SPAs): ```bash laravel new mi-proyecto // Selecciona "React" cuando te pregunte ``` Incluye: - React 19 + TypeScript - Inertia 2 (SSR listo) - shadcn/ui components - Tailwind CSS - Autenticación completa **Vue Starter Kit** (Para fans de Vue): Incluye: - Vue 3 Composition API + TypeScript - Inertia 2 - shadcn-vue components - Tailwind CSS - Autenticación completa **Livewire Starter Kit** (Full-stack sin JavaScript): Incluye: - Livewire 4 + Laravel Volt - Flux UI component library - Tailwind CSS - Ideal si prefieres quedarte en PHP **Svelte Starter Kit** (Nuevo en Laravel 13): Incluye: - Svelte 5 + TypeScript - Inertia 2 - Tailwind CSS - Autenticación completa > **Cambio importante:** Breeze y Jetstream fueron removidos del instalador `laravel new` en Laravel 13. Ahora los starter kits vienen integrados directamente. Si aún quieres usar Breeze, puedes instalarlo manualmente con `composer require laravel/breeze --dev`. **Para esta serie usaremos el Livewire Starter Kit** porque nos permite enfocarnos en Laravel sin necesitar JavaScript avanzado. ### Comando Artisan El comando artisan proporciona muchos comandos útiles que pueden acelerar tu ritmo de desarrollo. Puedes ver todos los comandos artisan disponibles ejecutando `php artisan list` o simplemente `php artisan`. ### Configuración básica Todos los archivos de configuración de Laravel para este proyecto se encuentran dentro del directorio `/config`, pero por ahora no nos centraremos en eso. La configuración básica de una aplicación Laravel gira en torno al archivo `.env`. El archivo `.env` contiene variables que pueden cambiar cuando movemos nuestra aplicación a otro entorno. Por ejemplo, cuando trasladamos nuestra aplicación de desarrollo al servidor de producción, nuestras credenciales de la base de datos seguramente cambiarán al igual que las de algunos otros servicios externos que estemos usando en nuestra aplicación. > Por eso se recomienda encarecidamente no incluir el archivo `.env` en un repositorio de git (hablaremos de git más adelante). ### Repasemos algunas de las variables `.env` vitales, cuándo y cómo usarlas. - `APP_NAME`: Este es el nombre de tu aplicación. Laravel utiliza este nombre de forma predeterminada, especialmente al enviar correos electrónicos. - `APP_ENV`: Se utiliza en Laravel para detectar dónde se está ejecutando tu aplicación. Cuando lo configuras en `production`, Laravel te mostrará una advertencia cada vez que realices una acción sensible, cómo ejecutar el comando `artisan migrate`. - `APP_KEY`: La clave de la aplicación se utiliza para asegurar la sesión y los datos encriptados. Por defecto, tu aplicación Laravel mostrará un error 500 si la clave no está configurada. - `APP_DEBUG`: Esta variable se establece en true de forma predeterminada y te permite ver la traza de errores. Se recomienda encarecidamente para entornos locales o de desarrollo. Si estableces esta variable en false, se activará la página de error predeterminada de Laravel y se ocultará la traza de errores. Esto es muy importante cuando estás en un entorno de producción. - `APP_URL`: Siempre establece esto como el nombre de dominio de tu aplicación. Laravel y algunos paquetes externos utilizan esta variable. ### Ejecución del proyecto Al ejecutar el comando `php artisan serve` en la consola obtendremos un resultado como este: ```bash php artisan serve INFO Server running on [http://127.0.0.1:8000]. Press Ctrl+C to stop the server ``` En el próximo artículo aprenderemos sobre [Rutas en Laravel](/post/aprende-laravel-rutas), el sistema que conecta URLs con tu código. > **¿Necesitas ayuda profesional con Laravel?** Ofrezco [servicios de desarrollo Laravel](/servicios/desarrollo-laravel) para aplicaciones enterprise, APIs RESTful y arquitectura escalable. ## Preguntas Frecuentes ### ¿Qué versión de PHP necesito para Laravel 13? Laravel 13 requiere **PHP 8.3 o superior**, aunque se recomienda PHP 8.3 para mejor rendimiento y características más recientes. Además necesitas las extensiones PHP: BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, y XML. ### ¿Cuál es la forma más rápida de instalar Laravel? **Laravel Herd** es la forma más rápida de comenzar con Laravel. Es una aplicación nativa para Windows/Mac que incluye PHP, Nginx y todo lo necesario con instalación en 1 click. Descárgalo desde [herd.laravel.com](https://herd.laravel.com). ### ¿Debo incluir el archivo .env en git? **No.** Se recomienda encarecidamente NO incluir el archivo `.env` en un repositorio de git porque contiene credenciales sensibles de base de datos y servicios externos que cambiarán entre entornos (desarrollo, staging, producción). ### ¿Qué diferencia hay entre Herd y Sail? **Herd** es una aplicación nativa (sin Docker) perfecta para principiantes - instalación en 1 click y muy rápido. **Sail** usa Docker, es ideal si necesitas un entorno consistente entre desarrollo y producción, o si usas Linux (Herd solo funciona en Windows/Mac). ### ¿Qué son los Starter Kits en Laravel 13? Laravel 13 rediseñó los Starter Kits con cuatro opciones **standalone** modernas: React (con shadcn/ui), Vue (con shadcn-vue), Livewire 4 (con Flux UI) y Svelte 5. Vienen integrados directamente en el instalador `laravel new` — ya no necesitas instalar Breeze o Jetstream manualmente. ### Video de la lección Ver video tutorial: [Aprende Laravel - Instalación y Setup](https://www.youtube.com/watch?v=8Fv2BNGLw_8) Playlist completa en YouTube: [Aprende Laravel @ YouTube](https://www.youtube.com/playlist?list=PLPFfjDS32gikCkR3s7pLN40MJuSlOFu6h) --- ### Cómo implementar Global Scopes en Laravel - URL: https://www.angelcruz.dev/post/como-implementar-los-global-scopes-usando-laravel - Markdown: https://www.angelcruz.dev/post/como-implementar-los-global-scopes-usando-laravel.md - Categoría: Laravel - Fecha: 2023-02-11 - Excerpt: En un artículo anterior explicaba como usar los query scopes de forma local, ahora, en este nuevo artículo te voy a mostrar como crear query scopes globales. --- title: "Cómo implementar Global Scopes en Laravel" excerpt: "En un artículo anterior explicaba como usar los query scopes de forma local, ahora, en este nuevo artículo te voy a mostrar como crear query scopes globales." date: "2023-02-11T22:19:42.000Z" category: "Laravel" seo_title: "Global Scopes en Laravel: aplicar filtros automáticos en Eloquent" seo_description: "Implementa Global Scopes en Laravel para aplicar condiciones automáticas a todas las consultas Eloquent de un modelo. Incluye ventajas, desventajas y cómo eliminarlos puntualmente." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- En este artículo vamos a continuar con el ejemplo anterior: ```php where('active', 1); } } ``` Para crear nuestro global scope vamos a crear una clase llamada `ActiveScope` y la ubicaremos en `app/Scopes` la estructura de ese global scope sería la siguiente: ```php where('active', 1); } } ``` Y la forma de implementarlo en el modelo es la siguiente: ```php get(); ``` Pero ahora con la implementación del global scope ya no es necesario, solo con hacer: ```php User::get(); ``` es suficiente para obtener todos los registros que tengan el estatus activo. Para remover un global scope en una consulta específica basta solamente con hacer lo siguiente: ```php User::withoutGlobalScope('active')->get(); ``` ### Desventaja del uso de los global scopes. - Complejidad adicional: Añadir global scopes puede hacer que tu código sea más complejo y difícil de entender y mantener. - Sobrecarga de memoria: Al utilizar global scopes, todas las consultas que se ejecuten para ese modelo incluirán automáticamente ese scope, lo que puede llevar a una sobrecarga de memoria en la aplicación. - Limitaciones en las consultas: Al utilizar global scopes, puede que no puedas realizar ciertas consultas o combinaciones de consultas que de otra manera serían posibles sin ellos. - Problemas de rendimiento: Dependiendo de la cantidad y complejidad de los global scopes que se hayan añadido, estos pueden ralentizar el rendimiento de la aplicación y hacer que las consultas se ejecuten más lentamente. - Dificultad para anular: A veces puede ser difícil anular un global scope en una consulta específica, lo que puede limitar la flexibilidad de tus consultas. Es importante tener en cuenta que aunque los global scopes tienen estos inconvenientes, también pueden ser útiles en algunos casos específicos para hacer que tu código sea más fácil de mantener y aplicar restricciones de seguridad de forma global. Por lo tanto, es importante evaluar cuidadosamente si son adecuados para tus necesidades antes de implementarlos. Para más información sobre la implementación de los query scopes visiten la documentacion: https://laravel.com/docs/9.x/eloquent#query-scopes --- ### Cómo se usan los Query Scopes - URL: https://www.angelcruz.dev/post/como-se-usan-los-query-scopes - Markdown: https://www.angelcruz.dev/post/como-se-usan-los-query-scopes.md - Categoría: Laravel - Fecha: 2023-02-05 - Excerpt: Los query scopes son una alternativa para optimizar nuestro código cuando necesitamos hacer condiciones específicas en nuestras consultas, aquí en este post te explico de que tratan. --- title: "Cómo se usan los Query Scopes" excerpt: "Los query scopes son una alternativa para optimizar nuestro código cuando necesitamos hacer condiciones específicas en nuestras consultas, aquí en este post te explico de que tratan." date: "2023-02-05T22:02:12.000Z" category: "Laravel" seo_title: "Laravel Scopes (Query Scopes): Cómo Crear Filtros Reutilizables en Eloquent" seo_description: "Los query scopes en Laravel Eloquent permiten encapsular condiciones SQL reutilizables en el modelo. Guía con ejemplos prácticos para crear scopes locales correctamente." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Una de las características más poderosas de Laravel son los query scopes ya que son fáciles de usar, los query scopes facilita la creación de consultas SQL complejas que en algunos casos se pueden volver a aplicar la misma condición a otros modelos dentro de tu aplicación. Aquí hay un ejemplo de código de cómo crear un _query scope_ en Laravel: ```php where('active', 1); } } ``` En el ejemplo anterior, creamos un _query scope_ llamado `Active` en el modelo `User`, donde basicamente lo que hace agregar una condición `WHERE` para extraer solo los usuarios que esten activos. La forma de usarlo sería la siguiente: ```php User::active()->get() ``` ### Consideraciones para usar los query scopes Para definir y usar correctamente los _query scope_, debemos seguir algunas reglas: - Todos los scopes deben recibir la variable `$query`. - Todos los nombres de los scopes deben comenzar con la palabra `scope` seguido del nombre que queremos llamarlo. En un próximo artículo voy a tratar de explicar una forma de organizar los scopes y hacer que sean "_IDE friendly_" 😏 Para más información visiten este enlace: https://laravel.com/docs/9.x/eloquent#query-scopes --- ### Script para hacer deploy de una aplicación Laravel usando Laravel Envoy - URL: https://www.angelcruz.dev/post/script-para-hacer-deploy-de-una-aplicacion-laravel-usando-laravel-envoy - Markdown: https://www.angelcruz.dev/post/script-para-hacer-deploy-de-una-aplicacion-laravel-usando-laravel-envoy.md - Categoría: Laravel - Fecha: 2023-01-28 - Excerpt: En este artículo te voy a mostrar como hacer deploy de tu aplicación Laravel en un VPS usando Laravel Envoy, es más sencillo de lo que piensas. 😎 --- title: "Script para hacer deploy de una aplicación Laravel usando Laravel Envoy" excerpt: "En este artículo te voy a mostrar como hacer deploy de tu aplicación Laravel en un VPS usando Laravel Envoy, es más sencillo de lo que piensas. 😎" date: "2023-01-28T15:24:04.000Z" category: "Laravel" seo_title: "Deploy de Laravel con Envoy: script completo para VPS" seo_description: "Automatiza el deploy de Laravel en un VPS con Laravel Envoy. Script Envoy.blade.php que activa modo mantenimiento, hace git pull, instala dependencias, migra y limpia caché en un solo comando." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Ok, para iniciar debes tener en cuenta que vas a necesitar ciertos conocimientos de administración de servidores pero no te preocupes que solo necesitas saber como instalar paquetes dentro de tu VPS. ### Lo que vas hacer es: - Loguarte dentro de tu VPS, haciendo ssh. - Dentro de tu VPS vas a instalar GIT `sudo apt get install git` (o con el gestor de paquetería de tu servidor, aca asumo que estás usando ubuntu) - Luego de instalar git tienes que clonar tu proyecto en un directorio específico. Yo te recomendaria clonar el proyecto dentro del directorio home de tu usuario en el VPS, creando una estructura parecida a esta: `/home/USUARIO_VPS/project`
Luego de esto lo único que tienes que hacer es ingresar a tu proyecto y hacer todo lo necesario para instalar las dependencias (composer) y hacer las configuraciones para la base de datos y todo lo que necesites. En este punto, si sigues mi recomendación de clonar el proyecto en tu directorio home el siguiente paso es cambiar el path del directorio root en el archivo de configuración de tu servidor web, nuevamente asumiendo que usas NGINX el cambio lo debes hacer en la siguiente línea ``` server { listen 80; listen [::]:80; server_name example.com; root /home/USUARIO_VPS/project/public; [tl! focus] add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; // ... } ``` ### En tu proyecto Vas a instalar envoy como una dependencia de desarrollo de la siguiente forma: ```shell composer require laravel/envoy --dev ``` Luego de esto, debes crear un archivo `Envoy.blade.php` donde vas a incluir la siguiente información: ```shell @servers(['main' => ['user@host_ip']]) @task('deploy', ['on' => 'main']) cd /home/USUARIO_VPS/project php artisan down git pull origin master composer install --optimize-autoloader --no-interaction --no-plugins --no-scripts --no-dev php artisan migrate --force php artisan cache:clear php artisan config:cache php artisan view:cache composer dumpautoload -o php artisan up @endtask ``` Los cambios que debes implementar son los siguientes: - Modificar el usuario y el host ip - Listo 🤣 Lo que hace el archivo creo que se explica solo pero por si acaso: - Ingresa al directorio del proyecto - Activa el modo mantenimiento - Hace un pull a tu proveedor de control de versiones para bajar los cambios mas recientes - Hace composer install para instalar las dependencias del proyecto (no incluye dependencias de desarrollo) - Ejecuta las migraciones (si existe algo para migrar) - Limpia y crea el cache - Optimiza el autoload de composer - Sale del modo de mantenimiento
Para ejecutar envoy puedes hacer lo siguiente: ```shell php vendor/bin/envoy ``` Espero que te sirva. --- ### Laravel: Error de permisos al intentar borrar el caché - URL: https://www.angelcruz.dev/post/laravel-error-de-permisos-al-intentar-borrar-el-cache - Markdown: https://www.angelcruz.dev/post/laravel-error-de-permisos-al-intentar-borrar-el-cache.md - Categoría: Laravel - Fecha: 2023-01-08 - Excerpt: Al hacer deploys automáticos con Envoy encontré un error, indicaba que no estaba limpiando el cache de la aplicación por problemas de permisos. Aquí te muestro como lo solucioné. --- title: "Laravel: Error de permisos al intentar borrar el caché" excerpt: "Al hacer deploys automáticos con Envoy encontré un error, indicaba que no estaba limpiando el cache de la aplicación por problemas de permisos. Aquí te muestro como lo solucioné." date: "2023-01-08T01:59:17.000Z" category: "Laravel" seo_title: "Solucionar error de permisos al borrar caché en Laravel" seo_description: "Corrige el error de permisos en php artisan cache:clear usando chown y chmod en los directorios storage y bootstrap/cache. Solución probada en deploys con Laravel Envoy." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Mi usuario para hacer los deploys se llama bender (si, como Bender de Futurama 🤣) por lo que al usar Envoy ejecutaba este comando: `php artisan cache:clear` . Para solucionar este problema, primero hay que revisar que el usuario bajo el cual se ejecuta PHP (www-data si usas Apache / NGINX) tenga permisos de escritura en el directorio de caché de tu aplicación. Y eso se puede hacer simplemente listando el directorio `storage` de la siguiente forma: ```bash bender@server:~/html/project$ ls -l storage/ total 24 drwxrwxr-x 3 www-data www-data 4096 Jun 12 2022 app drwxrwxr-x 2 www-data www-data 4096 Aug 6 19: 03 debugbar drwxrwxr-x 6 www-data www-data 4096 Jan 7 23: 05 framework drwxrwxr-x 2 www-data www-data 4096 Aug 6 19: 03 image drwxrwxr-x 2 www-data www-data 4096 Jun 12 2022 logs drwxrwxr-x 3 www-data www-data 4096 Aug 26 04: 46 media-library ``` En este caso, el usuario no es el mismo por lo que necesitaba cambiar ese comportamiento y para eso necesitaba la ayuda de los comandos `chown` y `chmod`: ```bash sudo chown -R $USER:www-data storage sudo chown -R $USER:www-data bootstrap/cache chmod -R 775 storage chmod -R 775 bootstrap/cache ``` Luego hay que verificar que el cambio se haya realizado correctamente y para eso usamos el mismo comando `ls -l`: ```bash bender@server:~/html/project$ ls -l storage/ total 24 drwxrwxr-x 3 bender www-data 4096 Jun 12 2022 app drwxrwxr-x 2 bender www-data 4096 Aug 6 19: 03 debugbar drwxrwxr-x 6 bender www-data 4096 Jan 7 23: 05 framework drwxrwxr-x 2 bender www-data 4096 Aug 6 19: 03 image drwxrwxr-x 2 bender www-data 4096 Jun 12 2022 logs drwxrwxr-x 3 bender www-data 4096 Aug 26 04: 46 media-library ``` Y con eso validamos que el cambio se hizo correctamente y ahora no tendremos problemas. Espero que esto te sirva de ayuda si también tienes algún problema parecido en algún momento. --- ### Definir middlewares de Laravel dentro de un paquete de composer. - URL: https://www.angelcruz.dev/post/definir-middlewares-de-laravel-dentro-de-un-paquete-de-composer - Markdown: https://www.angelcruz.dev/post/definir-middlewares-de-laravel-dentro-de-un-paquete-de-composer.md - Categoría: Laravel - Fecha: 2022-12-10 - Excerpt: Últimamente he estado trabajando en crear paquetes para composer para ser usados con Laravel y me encontré con la necesidad de agregar varios middlewares a otros grupos de middlewares. Redundante lo sé pero espero que con el ejemplo que te voy a dar puedas entender mejor 🤣 --- title: "Definir middlewares de Laravel dentro de un paquete de composer." excerpt: "Últimamente he estado trabajando en crear paquetes para composer para ser usados con Laravel y me encontré con la necesidad de agregar varios middlewares a otros grupos de middlewares. Redundante lo sé pero espero que con el ejemplo que te voy a dar puedas entender mejor 🤣" date: "2022-12-10T19:03:18.000Z" category: "Laravel" seo_title: "Registrar middlewares de Laravel en un paquete de Composer" seo_description: "Cómo agregar middlewares a grupos 'web' o 'api' de Laravel desde el ServiceProvider de un paquete Composer usando pushMiddlewareToGroup y aliasMiddleware." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Ok, entonces qué es un grupo de middleware? La respuesta es sencilla, si vas al archivo `app/Http/Kernel.php` ahí vas a poder encontrar un arreglo llamado `$middlewareGroups` que incluye las claves "web" y "api". Entendiendo ya los grupos de middleware ahora pasaré a explicar como agregar un middleware desde el service provider de un paquete. Es tan sencillo como hacer lo siguiente: ```php app['router']; $router->pushMiddlewareToGroup('web', CustomMiddleware::class); } } ``` También si quisieran agregar un alias para un middleware lo pueden hacer de esta forma: ```php app['router']; $router->aliasMiddleware( 'custom.middleware', CustomMiddleware::class ); } } ``` Y ya a partir de aquí queda de ustedes usarlo en las rutas o en el constructor de sus controladores. 😉 --- ### Notificaciones con laravel livewire - URL: https://www.angelcruz.dev/post/notifiaciones-con-laravel-livewire - Markdown: https://www.angelcruz.dev/post/notifiaciones-con-laravel-livewire.md - Categoría: Laravel - Fecha: 2022-03-06 - Excerpt: Con livewire se pueden despachar eventos al navegador para ser detectados con javascript y poder realizar algún tipo de acción en el front end. --- title: "Notificaciones con laravel livewire" excerpt: "Con livewire se pueden despachar eventos al navegador para ser detectados con javascript y poder realizar algún tipo de acción en el front end." date: "2022-03-06T21:01:50.000Z" category: "Laravel" seo_title: "Notificaciones en Livewire con Alpine.js y browser events" seo_description: "Implementa notificaciones toast en Livewire usando dispatchBrowserEvent y Alpine.js. Tutorial con cierre automático por timeout y reutilización vía session()->flash en cualquier parte del proyecto." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ### Despachando eventos en el navegador Entonces básicamente la documentación de livewire indica que hay que hacer algo como esto: ```php $this->dispatchBrowserEvent('name-updated', ['newName' => $value]); ``` Y con javascript podemos detectar ese evento: ```js ``` ### Ejemplo práctico Supongamos que queremos levantar una notificación cuando se ejecuta una acción con un componente: Lo que tenemos que hacer es despachar el evento con ciertas características que nos permitan hacer de nuestra notificación funcional y para eso haremos esto: ```php $this->dispatchBrowserEvent('notification', [ 'body' => 'El registro fue actualizado correctamente', 'timeout' => 4000 ]); ``` Aca basicamente le indicamos a nuestra notificación que va a tener un cuerpo (body) y un tiempo de espera (timeout) de 4 segundos para que se cierre automáticamente. Y nuestra notificacion va a lucir de esta manera: ```html
``` ### DRY También podemos re usar la misma notificación sin importar que estemos livewire, por ejemplo, en alguna parte de nuestra aplicación podemos tener algo como esto: ```php session()->flash('notification', 'Texto de notificación'); ``` Los cambios que tenemos que hacer son los siguientes: ```html
``` Nota: esto lo saque de un curso de https://codecourse.com/ --- ### Obtener sugerencias de keywords desde google usando el cliente HTTP de Laravel. - URL: https://www.angelcruz.dev/post/obtener-sugerencias-de-keywords-desde-google-usando-el-cliente-http-de-laravel - Markdown: https://www.angelcruz.dev/post/obtener-sugerencias-de-keywords-desde-google-usando-el-cliente-http-de-laravel.md - Categoría: Laravel - Fecha: 2022-02-12 - Excerpt: Las keywords son términos utilizados en los buscadores para expresar la información que los usuarios quieren encontrar en Internet. --- title: "Obtener sugerencias de keywords desde google usando el cliente HTTP de Laravel." excerpt: "Las keywords son términos utilizados en los buscadores para expresar la información que los usuarios quieren encontrar en Internet." date: "2022-02-12T20:28:03.000Z" category: "Laravel" seo_title: "Obtener sugerencias de keywords de Google con Laravel HTTP" seo_description: "Consulta la API de sugerencias de Google con el cliente HTTP de Laravel en una función PHP reutilizable. Devuelve hasta 10 keywords relacionadas en un array sin claves de API ni autenticación." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Para hacer la petición de sugerencias de keywords a google deberemos hacer lo siguiente: ```php json(); if (($data = $jsonData) !== null) { $keywords = $data[1]; } return $keywords; } } ``` Su forma de usarlo es muy simple, solo debemos hacer algo como esto: ```php $keywords = suggestKeyword('php'); ``` Y vamos a obtener como respuesta algo parecido: ```shell Array ( [0] => php [1] => phpmyadmin [2] => php date [3] => phpstorm [4] => php online [5] => php array length [6] => php foreach [7] => phpunit [8] => php array [9] => php try catch ) ``` Espero que esto les sirva --- ### Como implementar Actions en Laravel - URL: https://www.angelcruz.dev/post/como-implementar-actions-en-laravel - Markdown: https://www.angelcruz.dev/post/como-implementar-actions-en-laravel.md - Categoría: Laravel - Fecha: 2022-02-10 - Excerpt: Que son las actions? Pues basicamente son clases que se encargan de tareas especificas dentro de nuestra aplicación. --- title: "Como implementar Actions en Laravel" excerpt: "Que son las actions? Pues basicamente son clases que se encargan de tareas especificas dentro de nuestra aplicación." date: "2022-02-10T03:05:21.000Z" category: "Laravel" seo_title: "Implementar Actions en Laravel: clases invocables con DI" seo_description: "Cómo implementar el patrón Actions en Laravel con clases invocables, interfaces y service providers. Mantén tu lógica de negocio aislada y testeable con Single Action Controllers." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Tomemos el ejemplo de crear un blog y necesitamos crear articulos y para eso vamos a crear dentro de nuestra aplicacion varias carpetas y archivos para lograr nuestro cometido. Primero creemos los siguientes directorios, dentro de app tendremos: `Actions/Article` y vamos a crear un archivo llamado `ArticleCreate` y quedaria de la siguiente forma: ```php $input['title'], 'body' => $input['body'], ]); } } ``` Con este cambio dejamos listo nuestra accion para crear articulos. Tenemos ahora que hacer la implementacion de nuestra accion para hacer el uso de ella. Para implementar la accion vamos hacer uso de los "Single Action Controllers" y haremos lo siguiente: ```php all()); } } ```
Necesitamos crear un service provider con lo siguiente: ```php ArticleCreateAction::class, ]; } ```
Y ya para finalizar tengo que agradecer a Luke Downing y su charla en el Laracon "Actions are a Dev's Best Friend", para el mi agradecimiento porque su charla me motivo a realizar este pequeño articulo y refactorizar este blog. Btw, visiten a Luke en su perfil de twitter: @LukeDowning19 --- ### Como usar Ping-O-Matic con Laravel - URL: https://www.angelcruz.dev/post/como-usar-ping-o-matic-con-laravel - Markdown: https://www.angelcruz.dev/post/como-usar-ping-o-matic-con-laravel.md - Categoría: Laravel - Fecha: 2022-01-30 - Excerpt: Ping-O-Matic es un servicio que permite notificar a los motores de busqueda que hemos publicado un nuevo artículo. --- title: "Como usar Ping-O-Matic con Laravel" excerpt: "Ping-O-Matic es un servicio que permite notificar a los motores de busqueda que hemos publicado un nuevo artículo." date: "2022-01-30T21:29:42.000Z" category: "Laravel" seo_title: "Notificar a motores de búsqueda con Ping-O-Matic en Laravel" seo_description: "Usa el cliente HTTP de Laravel para notificar a Ping-O-Matic cuando publicas contenido nuevo. Integración en menos de 10 líneas de código con el wrapper HTTP de Laravel." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Lo que necesitamos es usar el wrapper HTTP de laravel. Ping-O-Matic necesita un "formato" específico para postear la información. ### Los parámetros que necesitamos son: - title - blogurl - rssurl - chk_blogs - chk_feedburner - chk_tailrank - chk_superfeedr ### wrapper http Sabiendo lo que necesitamos entonces tenemos que crear la funcionalidad que se va a encargar de revisar las publicaciones nuevas y lo haremos de esta forma: ```php Http::asForm()->post('https://pingomatic.com/ping', [ 'title' => urlencode(config('app.name')), 'blogurl' => urlencode(config('app.url')), 'rssurl' => urlencode(url('feeds.main')), 'chk_blogs' => 'on', 'chk_feedburner' => 'on', 'chk_tailrank' => 'on', 'chk_superfeedr' => 'on', ]); ``` Y ya con esto tenemos listo nuestra "integración" a Ping-O-Matic --- ### Crear OG images con laravel y browsershot - URL: https://www.angelcruz.dev/post/crear-og-images-con-laravel-y-browsershot - Markdown: https://www.angelcruz.dev/post/crear-og-images-con-laravel-y-browsershot.md - Categoría: Laravel - Fecha: 2022-01-17 - Excerpt: Hay muchos servicios por ahí que sirven para crear este tipo de imágenes pero para no depender de ellos usaremos browsershot, que es un paquete creado por la gente de spatie. --- title: "Crear OG images con laravel y browsershot" excerpt: "Hay muchos servicios por ahí que sirven para crear este tipo de imágenes pero para no depender de ellos usaremos browsershot, que es un paquete creado por la gente de spatie." date: "2022-01-17T02:28:00.000Z" category: "Laravel" seo_title: "Generar OG Images automáticamente en Laravel con Browsershot" seo_description: "Crea Open Graph images dinámicas en Laravel usando Browsershot y Puppeteer. Genera screenshots desde vistas Blade, guárdalos en storage y actualiza la base de datos con un comando Artisan." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Primero que todo es importante seguir los pasos de instalación del paquete que en su [documentacion](https://github.com/spatie/browsershot) explica como hacerlo.
### como se usa Asumiendo que leyeron la documentación del propio paquete ya saben como se usa, por lo que voy a pasar a explicarles un ejemplo práctico En mi caso tengo una serie de publicaciones en este blog al cual quería actualizar la "og image" y lo que hice fue crear un comando para hacerlo automatico. ## hagamos la magia ![](https://media.tenor.com/DNCBqbguizsAAAAC/magic.gif) Primero creamos el comando: ```shell php artisan make:command GenerateOgImage ``` Cambiamos la firma del comando a algo como esto: ```php protected $signature = 'generate:ogimage'; ``` No se olviden de que tienen que ejecutar: ```shell php artisan storage:link ``` En mi caso, tengo un campo en la base de datos llamado `file` el cual vamos a actualizar con el nuevo valor correspondiente a la imagen que vamos a crear. Ahora pasamos hacer lo siguiente: ```php /** * Execute the console command. * * @return int */ public function handle() { $articles = Article::get(); $articles->each(function ($item, $key) { $item->file = $this->browswerShoot($item); $item->save(); }); return self::SUCCESS; } ``` Lo que hacemos aquí es simplemente es encontrar todos los artículos e iterar por cada uno de ellos para actualizar el campo `file` Y ya para finalizar: ```php public function browswerShoot($item) { $html = view('ogImage', ['post' => $item])->render(); $imageData = Browsershot::html($html) ->devicePixelRatio(2) ->windowSize(1200, 630) ->screenshot(); $this->saveOgImage($imageData, $item->slug); return $this->ogImageUrl($item->slug); } public function saveOgImage(string $file, $param) { Storage::disk('public')->put($this->ogImagePath($param), $file); } public function ogImagePath($param): string { return "post/{$param}.png"; } public function ogImageUrl($param): string { return Storage::disk('public')->url($this->ogImagePath($param)); } ``` Y bueno, creo que todo esto se explica solo pero por si acaso: Creamos una instancia de `browsershot` donde le indicamos una vista que va a renderizar y le pasamos una variable, esta variable contiene el título de nuestro artículo. a partir de este momento, `browsershot` se encarga de hacer su magia y genera el screenshot y guarda la imagen en una carpeta llamada `post` y el nombre de la imagen será el slug de dicho artículo y ya por ultimo lo que hacemos es retornar la url de la imagen para que sea actualizado en nuestra base de datos. Espero que les sea de utilidad. --- ### No eres senior. Ya, eres muy bueno en Z. Pero no eres senior. - URL: https://www.angelcruz.dev/post/no-eres-senior-ya-eres-muy-bueno-en-z-pero-no-eres-senior - Markdown: https://www.angelcruz.dev/post/no-eres-senior-ya-eres-muy-bueno-en-z-pero-no-eres-senior.md - Categoría: Opinión - Fecha: 2022-01-17 - Excerpt: Un pequeño post escrito en linkedin por Gedeón Domínguez Torán, CEO @CloudDistrict; que me pareció muy acertado y quisiera compartirlo con todos ustedes. --- title: "No eres senior. Ya, eres muy bueno en Z. Pero no eres senior." excerpt: "Un pequeño post escrito en linkedin por Gedeón Domínguez Torán, CEO @CloudDistrict; que me pareció muy acertado y quisiera compartirlo con todos ustedes." date: "2022-01-17T01:04:29.000Z" category: "Opinión" seo_title: "No eres senior: seniority va más allá del nivel técnico" seo_description: "El seniority se mide por cómo resuelves problemas, apoyas al equipo y gestionas la incertidumbre, no por dominar un lenguaje. Un análisis por niveles: junior, mid y senior." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ### Seniority por nivel entrópico: - Junior: Tiende a generar más problemas de los que resuelve. - Mid: Equilibrado, genera y resuelve a partes iguales. - Senior: Resuelve mucho más de lo que genera. ### Seniority por forma de afrontar una dificultad: - Junior: me ayudas? - Mid: me apaño - Senior: Investigo, soluciono, documento y se lo explico a mis compañeros para que todos mejoremos. ### Seniority por disposición hacia un compañero en apuros: - Junior: Yo sigo que anda que no tengo jaleo yo solo - Mid: Te paso un link de una posible solución por Slack - Senior: Dame un minuto que acabo un tema, me acerco y lo vemos. ### Seniority de cara a la organización: - Junior: Esto X es una mierda y un caos - Mid: Podríamos mejorar esto X si Q y Z - Senior: X es una puta mierda. Yo me encargo. ### Seniority por actitud: - Junior: x!!Z..AAA!!!!!!!! - Mid: x!!Z..AAA!!!!!!!!…. Perdón que llevo un día de mierda. - Senior. Mejor me callo que llevo un día de mierda y solo voy a soltar mierda. ### Por autoformación: - Junior: Si necesito aprender algo pregunto - Mid: leo lo que me pasan, lo que me ofrece la empresa y lo que veo en las redes que puede ser interesante. - Senior: Leo sistemáticamente, tengo temas de interés que voy profundizando, pido libros a la empresa, los leo y extraigo fragmentos que comparto con mis colegas. Sugiero a la empresa esta formación o la otra. Y por nivel técnico? Es casi irrelevante. Hazte senior en todo esto y no tardarás en tener un gran nivel técnico. --- ### Script para configurar docker y docker-compose - URL: https://www.angelcruz.dev/post/script-para-configurar-docker-y-docker-compose - Markdown: https://www.angelcruz.dev/post/script-para-configurar-docker-y-docker-compose.md - Categoría: DevOps - Fecha: 2021-07-01 - Excerpt: Docker Compose es una herramienta que permite simplificar el uso de Docker. A partir de archivos YAML es más sencillo crear contenedores, conectarlos, habilitar puertos, volumenes, etc. --- title: "Script para configurar docker y docker-compose" excerpt: "Docker Compose es una herramienta que permite simplificar el uso de Docker. A partir de archivos YAML es más sencillo crear contenedores, conectarlos, habilitar puertos, volumenes, etc." date: "2021-07-01T02:40:45.000Z" category: "DevOps" seo_title: "Script bash para instalar Docker y Docker Compose en Ubuntu" seo_description: "Script bash que instala Docker y Docker Compose automáticamente en Ubuntu. Verifica si ya están instalados, descarga la versión indicada como argumento y añade el usuario al grupo docker." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Docker Compose es una herramienta que permite simplificar el uso de Docker. A partir de archivos YAML es más sencillo crear contenedores, conectarlos, habilitar puertos, volumenes, etc. ### script de instalación ```bash #!/bin/bash # // Viernes, Junio 28/2018 // Developed by angel if [[ $USER != root ]]; then echo "############################################" echo "# Error: Debe tener privilegios de ROOT ###" echo "##########################################" exit 1 fi set -eu export DEBIAN_FRONTEND=noninteractive # // verificamos si tenemos Docker. command -v docker >/dev/null 2>&1 || { echo >&2 "Configurando requisitos para Docker..." apt-get update --fix-missing > /dev/null 2>&1 curl -sSL https://get.docker.com/ | sh > /dev/null 2>&1 sleep 4.0 echo >&2 "Listo..." } # // configurando docker-compose command -v docker-compose >/dev/null 2>&1 || { echo >&2 "Configurando docker-compose" COMPOSE_VERSION=$1 curl -L https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose > /dev/null 2>&1 chmod +x /usr/local/bin/docker-compose sleep 4.0 echo >&2 "Listo..." } echo >&2 "Configurando usuario en el grupo docker..." sudo usermod -aG docker ${USER} sleep 4.0 echo >&2 "Listo..." echo "#############################################" echo "## Se ha configurado el sistema con Docker #" echo "###########################################" exit 0; ``` ### como se usa? En primer lugar, se recomienda que el usuario tenga privilegios de root: ```bash sudo chmod +x docker-config.sh ``` Luego hay que ir a https://github.com/docker/compose/releases y tomar la versión del stable release que al momento es 1.29.2 y pasarla como argumento al script ```bash sudo ./docker-config.sh 1.21.2 ``` Y listo, ya tenemos docker y docker compose instalado en nuestro equipo. --- ### Postear un documento con formato XML usando el cliente HTTP de laravel - URL: https://www.angelcruz.dev/post/postear-un-documento-con-formato-xml-usando-el-cliente-http-de-laravel - Markdown: https://www.angelcruz.dev/post/postear-un-documento-con-formato-xml-usando-el-cliente-http-de-laravel.md - Categoría: Laravel - Fecha: 2021-05-21 - Excerpt: Para este post vamos a usar Twingly como ejemplo, que es un servicio que funciona para hacer ping para notificar que el contenido de nuestro blog fue actualizado, usa el protocolo XML-RPC y el formato de documentos con XML para procesar la información. --- title: "Postear un documento con formato XML usando el cliente HTTP de laravel" excerpt: "Para este post vamos a usar Twingly como ejemplo, que es un servicio que funciona para hacer ping para notificar que el contenido de nuestro blog fue actualizado, usa el protocolo XML-RPC y el formato de documentos con XML para procesar la información." date: "2021-05-21T15:59:21.000Z" category: "Laravel" seo_title: "Enviar XML con el cliente HTTP de Laravel: ping a Twingly" seo_description: "Usa el método send() del cliente HTTP de Laravel para hacer pings XML-RPC a Twingly y notificar actualizaciones de tu blog. Código completo con la estructura de la petición y respuesta esperada." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ![twingly](https://pbs.twimg.com/profile_images/665140754185125888/H2uRZcej_400x400.png) Laravel tiene un wrapper HTTP para hacer peticiones que funciona desde la versión 7 del framework. El cliente HTTP implementa (entre otras cosas), un método llamado `send` que posee la siguiente firma: ```php send(string $method, string $url, array $options = []) ``` Entonces, para hacer el ping a twingly solo debemos hacer lo siguiente: ```php $xml = ' weblogUpdates.ping '. config('app.name') .' '. config('app.url') .' '; Http::withHeaders([ 'Content-Type' => 'text/xml; charset=utf-8' ])->send('POST', 'https://rpc.twingly.com/', [ 'body' => $xml, ]); ``` Si la petición fue correcta vamos a obtener una respuesta de este tipo: ```xml flerror 0 message Thanks for the ping. ``` Los invito a leer la [documentación completa de twingly](https://developer.twingly.com/resources/rpc-ping/). --- ### Librería php para usar twitter - URL: https://www.angelcruz.dev/post/libreria-php-para-usar-twitter - Markdown: https://www.angelcruz.dev/post/libreria-php-para-usar-twitter.md - Categoría: PHP - Fecha: 2021-05-20 - Excerpt: Encontré una pequeña librería para php que sirve para trabajar con twitter que fue escrita por David Grudl. Realmente es increíble lo simple de usarla. Aquí les cuento un poco más. --- title: "Librería php para usar twitter" excerpt: "Encontré una pequeña librería para php que sirve para trabajar con twitter que fue escrita por David Grudl. Realmente es increíble lo simple de usarla. Aquí les cuento un poco más." date: "2021-05-20T03:40:39.000Z" category: "PHP" seo_title: "Librería PHP para publicar en Twitter: dg/twitter-php" seo_description: "Publica tweets desde PHP con la librería dg/twitter-php en una sola línea de código. Instalación con Composer, configuración de claves OAuth y envío de mensajes en UTF-8." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" --- ![twitter](https://cdn.cms-twdigitalassets.com/content/dam/help-twitter/logos/card_wide_blue.png.twimg.768.png) Twitter para PHP es una biblioteca muy pequeña y fácil de usar para enviar mensajes a Twitter y recibir actualizaciones de estado. Requiere PHP 5.4 o más reciente con extensión CURL y usa la nueva licencia BSD (BSD 3-Clause "New" or "Revised" License). #### instalación y configuración pues es muy facil, con el viejo y siempre fiel composer: ```bash composer require dg/twitter-php ``` Para configurarlo hay que iniciar sesión en https://twitter.com y registrar una aplicación desde la página https://apps.twitter.com. Hay que hacer clic en el enlace Mi token de acceso de la barra lateral y obtener dicho token. Ahora con: - consumer key, - consumer secret, - access token - secret access token creamos un objeto de la clase `twitter` de la siguiente forma y enviamos un mensaje: ```php use DG\Twitter\Twitter; $twitter = new Twitter($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret); // The send() method updates your status. The message must be encoded in UTF-8: $twitter->send('I am fine today.'); ``` Y listo, ya podemos enviar mensajes a twitter usando php. El enlace de la librería es [https://github.com/dg/twitter-php](https://github.com/dg/twitter-php). --- ### El micro formato h-entry - URL: https://www.angelcruz.dev/post/el-micro-formato-h-entry - Markdown: https://www.angelcruz.dev/post/el-micro-formato-h-entry.md - Categoría: Web - Fecha: 2021-05-10 - Excerpt: Implementa h-entry en tu blog: microformato estándar para contenido episódico. Mejora la sindicación y SEO de tus publicaciones con este tutorial. --- title: "El micro formato h-entry" excerpt: "Implementa h-entry en tu blog: microformato estándar para contenido episódico. Mejora la sindicación y SEO de tus publicaciones con este tutorial." date: "2021-05-10T00:18:13.000Z" category: "Web" seo_title: "h-entry: microformato W3C para contenido episódico en blogs" seo_description: "Implementa el microformato h-entry en tu blog para mejorar la sindicación y compatibilidad IndieWeb. Guía con ejemplos HTML y herramientas de validación como indiewebify.me." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![microformats](https://upload.wikimedia.org/wikipedia/commons/6/60/Microformat-logo.png) ### Ejemplo de uso ```html

Microformats are amazing

Published by W. Developer on

In which I extoll the virtues of using microformats.

Blah blah blah

``` ### Validar tu micro formato Existen varios sitios webs para validar la correcta implementación de h-entry: - [https://indiewebify.me/validate-h-entry](https://indiewebify.me/validate-h-entry) ### Retrocompatibilidad Para obtener compatibilidad con versiones anteriores de implementaciones que consumen hAtom 0.1 heredado, se pueden usar nombres de clase (o valores rel) de hAtom 0.1 además de las propiedades de h-entry más preparadas para el futuro, por ejemplo: ```html

A Tale Of Two Tags

It was the best of visible tags, it was the alternative invisible tags.

The a tag is perhaps the best of HTML, yet its corresponding invisible link tag has uses too.

General
``` Se puede utilizar cualquier elemento HTML5 válido. Los elementos de tipo article h1 address time, se utilizan en el ejemplo como sugerencias semánticamente más ricas, sin embargo, en general, las etiquetas div / span también funciona bien. Sin embargo, el elemento datetime es especial en el sentido de que su atributo de fecha y hora proporciona una forma más sencilla para el autor / usuario de separar una fecha y hora de tipo ISO8601 legible por máquina a una que sea legible por humanos. --- ### Genera URLs usando eloquent en laravel de forma sencilla - URL: https://www.angelcruz.dev/post/genera-urls-usando-eloquent-en-laravel-de-forma-sencilla - Markdown: https://www.angelcruz.dev/post/genera-urls-usando-eloquent-en-laravel-de-forma-sencilla.md - Categoría: Laravel - Fecha: 2021-05-03 - Excerpt: Hace unos días, en laravel news apareció un tutorial de Jordan Dalton donde explica como generar urls usando eloquent y en base a ese tutorial voy a mostrarles como lo adapté a mi proyecto. --- title: "Genera URLs usando eloquent en laravel de forma sencilla" excerpt: "Hace unos días, en laravel news apareció un tutorial de Jordan Dalton donde explica como generar urls usando eloquent y en base a ese tutorial voy a mostrarles como lo adapté a mi proyecto." date: "2021-05-03T03:17:44.000Z" category: "Laravel" seo_title: "Generar URLs dinámicas en Laravel Eloquent con UrlPresenter" seo_description: "Genera URLs en Laravel Eloquent con una clase UrlPresenter reutilizable que acepta el nombre del modelo como parámetro, evitando crear una clase separada por cada recurso." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Antes de seguir leyendo les recomiendo que vayan al artículo de Laravel News para que entiendan lo que voy hablar. Para eso hagan clic aquí [en este link](https://laravel-news.com/leverage-eloquent-to-prepare-your-urls). Ok, ahora les contaré lo que hice: Básicamente lo que hice fue modificar la clase `UrlPresenter` para que aceptara un parámetro adicional y quedó de la siguiente forma: ```php model = $model; $this->modelName = $modelName; } public function __get($key) { if(method_exists($this, $key)) { return $this->$key(); } return $this->$key; } public function show(): string { return route("{$this->modelName}.show", $this->model); } } ``` Y en el modelo usuarios coloqué esto: ```php public function getUrlAttribute() { return new UrlPresenter($this, 'users'); } ``` Las razón? Pues simplemente porque no quería crear una clase por cada modelo en el que estoy trabajando y al pasarle el nombre del modelo (en este ejemplo "users") puedo generar todas las url que necesito sin ningun tipo de problema. La forma de usarlo sería la misma como lo explicó Jordan: ```html {{ $user->name }} ``` Probablemente existe una mejor forma de hacerlo, si la conoces puedes decirme? Espero les sirva este post!! --- ### Qué hacer cuando necesitas subir una app de Laravel a un hosting compartido? - URL: https://www.angelcruz.dev/post/que-hacer-cuando-necesitas-subir-aun-app-de-laravel-a-un-hosting-compartido - Markdown: https://www.angelcruz.dev/post/que-hacer-cuando-necesitas-subir-aun-app-de-laravel-a-un-hosting-compartido.md - Categoría: Laravel - Fecha: 2021-04-07 - Excerpt: Es un proceso un sencillo que siguiendo estos pasos podrás hacer sin muchos problemas --- title: "Qué hacer cuando necesitas subir una app de Laravel a un hosting compartido?" excerpt: "Es un proceso un sencillo que siguiendo estos pasos podrás hacer sin muchos problemas" date: "2021-04-07T04:02:53.000Z" category: "Laravel" seo_title: "Subir Laravel a hosting compartido con cPanel paso a paso" seo_description: "Despliega una app Laravel en hosting compartido cPanel: configura la versión de PHP >= 7.1, ajusta los paths de vendor y bootstrap en index.php y enlaza public_html con AppServiceProvider." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ![Laravel](https://miro.medium.com/max/4392/1*GU1-L107_4rdTmvsBpO3Gw.png) Lo primero que debemos saber es: Qué versión de php requiere el framework Laravel para que funcione de la mejor manera; En este caso es PHP >= 7.1.3. Se está tomando este criterio como el primer aspecto ya que puedes tener los siguientes pasos correctamente que si no se ha fijado la versión del php que sea la correcta no va a funcionar el sitio, y es entonces cuando llegan los dolores de cabeza. Cómo sabemos que versión de php está utilizando nuestro hosting. Para ello entramos en el Cpanel y nos dirigimos a un botón llamado Select Php Version, una vez dentro se mostrará un combobox con las versiones de php soportadas por el hosting, para este framework con la versión 7.1 podremos trabajar sin problemas. Un tip de este hosting para que el mismo muestre los errores de php es el siguiente. Dentro del Cpanel buscamos un botón llamado MultiPHP INI Editor luego escogemos en el combobox que aparece nuestro dominio y después la opción display _errors la cambiamos a true. De esta manera cuando el framework nos envíe un mensaje de error vamos a saber cual es el problema en la consola. Lo siguiente será crear una carpeta en el root del hosting con el nombre que deseamos, en nuestro caso la hemos llamado laravel Ahora en la carpeta donde está nuestro sistema seleccionaremos todos los ficheros y carpetas menos la carpeta llamada public y creamos un .zip con los mismos llamada (zip1). Luego de esto se realiza lo mismo con los ficheros que están dentro de la carpeta public en este caso sería (zip2). Los archivos que están en zip1 hay que copiarlos en la carpeta llamada laravel que creamos en el root del hosting mientras que el zip2 será para la html_public del hosting, estas tareas las realizamos utilizando el File Manager del hosting ya que si utilizan algún gestor ftp no necesitan compactar los archivos, solo copiar todos los archivos y carpetas de nuestro servidor en las carpetas laravel y html_public. El siguiente paso es editar el index.php que se encuentra en el html_public y cambiar la dirección donde van a estar las carpetas de bootstrap y vendor del framework, para esto buscamos las dos líneas correspondientes a lo antes mencionado en el fichero index.php y agregamos después de los dos `../laravel/` quedando de la siguiente manera. ```php require __DIR__.'/../laravel/vendor/autoload.php'; $app = require_once __DIR__.'/../laravel/bootstrap/app.php'; ``` Luego de esto vamos a la carpeta laravel creada en el root del hosting, a la siguiente dirección: `laravel/app/providers/AppServiceProvider.php` y editamos dicho fichero, en la función llamada register agregamos el siguiente código, que es para decirle al framework que el nombre de la carpeta public cambió el nombre y el pueda utilizarla para sus funciones específicas. ```php $this->app->bind('path.public', function() { return base_path().'/public_html'; }); ``` Con esta configuración la carpeta public_html quedará dentro de la carpeta laravel que es su raíz, en caso de querer cambiar la dirección para que los archivos no se guarden dentro de la raíz, se tendrá que cambiar el nombre de public_html por el nombre de la carpeta en que se deseen alojar los archivos. Por ejemplo: Si queremos subir imágenes al servidor pero que las mismas puedan ser vistas desde una url tenemos que alojarlas en donde están los archivos públicos del laravel, en este caso sería el dominio. Cómo quedaría: ```php $this->app->bind('path.public', function() { return base_path().'/../mi_dominio.com'; }); ``` Ya realizado estos cambios se puede comprobar si el sistema está funcionando correctamente. Un video para que entiendan mejor: [https://www.youtube.com/watch?v=ejcClKFLrW0](https://www.youtube.com/watch?v=ejcClKFLrW0) Fuente original: https://tuponcho.com/subir-laravel-5-6-a-hosting-compartido-cpanel/ --- ### Como demostrar que realmente hiciste una app o página web. - URL: https://www.angelcruz.dev/post/como-demostrar-que-realmente-hiciste-una-app-o-pagina-web - Markdown: https://www.angelcruz.dev/post/como-demostrar-que-realmente-hiciste-una-app-o-pagina-web.md - Categoría: Opinión - Fecha: 2021-01-24 - Excerpt: Alguna vez pensaste cómo sería una buena forma de mostrar que fuiste el developer que trabajó muchas horas en la creación de esa página web que te llevó bastante tiempo? Aquí te voy a contar algo que puede ser que te sirva. --- title: "Como demostrar que realmente hiciste una app o página web." excerpt: "Alguna vez pensaste cómo sería una buena forma de mostrar que fuiste el developer que trabajó muchas horas en la creación de esa página web que te llevó bastante tiempo? Aquí te voy a contar algo que puede ser que te sirva." date: "2021-01-24T22:54:57.000Z" category: "Opinión" seo_title: "Demostrar que hiciste una app: página de perfil con Laravel" seo_description: "Muestra tu autoría como developer con el paquete its-my-code para Laravel. Crea una página de perfil con tu GitHub, repos y datos usando el cliente HTTP de Laravel." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Y si, muchos seguro ya están pensando que con la alternativa de http://humanstxt.org/ es suficiente, o que con esta meta etiqueta: ```html ``` O colocando un link en el footer que lleve a tu página web. Y si, todo eso está bien pero me encontré con un paquete que permite mostrar por medio de una vista la información del developer. Intente probar el paquete en una instalación nueva de laravel pero me dió un error y pues me puse a revisarlo y me encontré con algunas cosas que se pueden mejorar y es de lo que voy hablar en este post. Básicamente el controlador original del paquete lo que hace es hacer una petición http por medio de curl al api de github para extraer información del perfil del developer. Aprovechando desde hace algunas versiones laravel posee un cliente http me dispuse a mejorar esa consulta con curl y quedó de la siguiente forma: ```php public function getDataFromGithub() { $append = 'client_id=' .config('its-my-code.GITHUB_CLIENT_ID'). '&client_secret=' .config('its-my-code.GITHUB_CLIENT_SECRET'); $repos = $this->makeRequest('/repos?sort=pushed&'.$append); $user = $this->makeRequest('?' .$append); return [ 'user' => $user, 'repos' => $repos, ]; } public function makeRequest(string $param) { $response = Http::get('https://api.github.com/users/' . config('its-my-code.GITHUB_USERNAME') . $param); return $response->json(); } ``` El archivo de configuración del paquete cuenta con varias opciones para mostrar en el perfil ```php '', // your github username ( required ) 'INSTAGRAM_USERNAME' => null, // your instagram username 'LINKEDIN_USERNAME' => null, // your linkedin username 'REPO_COUNT' => 8, // number of repositories in page (int) 'MOBILE' => null, // mobile in string 'GITHUB_CLIENT_ID' => env('GITHUB_CLIENT_ID'), // see https://github.com/settings/developers 'GITHUB_CLIENT_SECRET' => env('GITHUB_CLIENT_SECRET'), // see https://github.com/settings/developers ]; ``` Y el resultado de todo eso sería algo como esto: ![](https://i.ibb.co/HF6GNvx/Screenshot-2021-01-24-Prove-the-programmer.png) Espero que esto les parezca útil. --- ### Cómo usar de forma sencilla mailchimp en wordpress. - URL: https://www.angelcruz.dev/post/como-usar-de-forma-sencilla-mailchimp-en-wordpress - Markdown: https://www.angelcruz.dev/post/como-usar-de-forma-sencilla-mailchimp-en-wordpress.md - Categoría: WordPress - Fecha: 2021-01-10 - Excerpt: Integra Mailchimp en WordPress fácilmente: procesa suscripciones a tu lista de correos sin complicaciones. Tutorial paso a paso con código de ejemplo. --- title: "Cómo usar de forma sencilla mailchimp en wordpress." excerpt: "Integra Mailchimp en WordPress fácilmente: procesa suscripciones a tu lista de correos sin complicaciones. Tutorial paso a paso con código de ejemplo." date: "2021-01-10T03:50:24.000Z" category: "WordPress" seo_title: "Integrar Mailchimp en WordPress sin plugins adicionales" seo_description: "Integra Mailchimp en WordPress usando wp_remote_post y la API v3. Procesa suscripciones con un formulario HTML simple y una plantilla PHP personalizada. Sin plugins de terceros." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Bueno, básicamente vamos a necesitar: - Un formulario html que solo va a tener un input y un botón, algo así: ```html
``` - Un archivo php que va a contener lo siguiente ```php $email, 'status' => 'subscribed', //pass 'subscribed' or 'pending' ]); $args = [ 'headers' => [ 'content-type' => 'application/json', 'accept' => 'application/json', 'cache-control' => 'no-cache', 'Authorization' => 'apikey ' . $apiKey ], 'body' => $json ]; $mailchimpResponse = wp_remote_post($url, $args); // wp_redirect(site_url('/thank-you/')); } else { wp_redirect('https://google.com'); } ``` - Una página donde mostrarle al usuario que su suscripción fue procesada y eso sería algo más o menos así: ```php >

Su suscripción fue procesada correctamente

``` Cosas para tener en cuenta: - tienes que crear una página que sirva como ruta para la acción del formulario usando la plantilla que puse arriba - cambia la redirección a la página de agradecimiento usando el slug correspondiente --- ### Como integrar webmentions usando laravel. - URL: https://www.angelcruz.dev/post/como-integrar-webmentions-usando-laravel - Markdown: https://www.angelcruz.dev/post/como-integrar-webmentions-usando-laravel.md - Categoría: Laravel - Fecha: 2021-01-08 - Excerpt: Implementa Webmention en Laravel: alternativa simple a pingback usando HTTP. Guía paso a paso para enlaces de retorno modernos en tu aplicación. --- title: "Como integrar webmentions usando laravel." excerpt: "Implementa Webmention en Laravel: alternativa simple a pingback usando HTTP. Guía paso a paso para enlaces de retorno modernos en tu aplicación." date: "2021-01-08T21:46:27.000Z" category: "Laravel" seo_title: "Integrar Webmentions en Laravel con caché y cliente HTTP" seo_description: "Implementa el protocolo Webmention en Laravel usando un Blade Component con caché de 6 horas. Gestiona likes, reposts y comentarios desde webmention.io de forma eficiente." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Webmention es una recomendación del W3C que describe un protocolo simple para notificar cualquier URL cuando un sitio web se vincula a él, y para que las páginas web soliciten notificaciones cuando alguien se vincula a ellas. Webmention se desarrolló originalmente en la comunidad IndieWebCamp y se publicó como borrador de trabajo del W3C el 12 de enero de 2016. A partir del 12 de enero de 2017, es una recomendación del W3C. Webmention permite a los autores realizar un seguimiento de quién enlaza, hace referencia o comenta sobre sus artículos. Al incorporar dichos comentarios de otros sitios, los propios sitios proporcionan una funcionalidad de comentarios federados. Conociendo ya de que trata esto de las webmentions me dediqué a buscar como hacerlo funcionar con laravel y encontré un post de [Tom Witkowski ](https://gummibeer.dev/blog/2020/blade-component-webmentions/) donde explica como hizo un componente de blade para gestionar sus webmentions. Les recomiendo que vayan y vean el artículo original para que entiendan lo que hizo Tom y comparen con los cambios que yo hice. #### Todo el componente es esto: ```php url(); $webmentions = collect(); $page = 0; do { $entries = Http::get('https://webmention.io/api/mentions.jf2', [ 'token' => config('services.webmention.token'), 'domain' => parse_url($url, PHP_URL_HOST), 'per-page' => 100, 'page' => $page, ])->json()['children'] ?? []; $webmentions->push(...$entries); $page++; } while (count($entries) >= 100); $webmentions = $webmentions ->filter(fn (array $entry): bool => trim(parse_url($entry['wm-target'], PHP_URL_PATH), '/') === trim(parse_url($url, PHP_URL_PATH), '/')); $key = 'article_likes_'.$url; if (config('app.env') != 'local') { $this->likes = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getLikes($webmentions); }); } $key = 'article_reposts_'.$url; if (config('app.env') != 'local') { $this->reposts = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getRepost($webmentions); }); } $key = 'article_comments_'.$url; if (config('app.env') != 'local') { $this->comments = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getComments($webmentions); }); } } /** * Get the view / contents that represent the component. * * @return \Illuminate\Contracts\View\View|string */ public function render() { return view('components.front.webmentions.webmentions'); } private function getComments($webmentions) { return $webmentions ->filter(fn (array $entry): bool => in_array($entry['wm-property'], ['mention-of', 'in-reply-to'])) ->reject(fn (array $entry): bool => empty($entry['content']['text'])); } private function getLikes($webmentions) { return $webmentions ->filter(fn (array $entry): bool => $entry['wm-property'] === 'like-of'); } private function getRepost($webmentions) { return $webmentions->filter(function (array $entry): bool { if ($entry['wm-property'] === 'repost-of') { return true; } if ($entry['wm-property'] === 'mention-of') { return empty($entry['content']['text']); } return false; }); } } ``` #### Ok, y que fue lo que cambió? Bueno, básicamente lo que hice fue guardar en cache la respuesta de los "likes", "comments" y "repost" de la siguiente forma: ```php $key = 'article_likes_'.$url; if (config('app.env') != 'local') { $this->likes = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getLikes($webmentions); }); } $key = 'article_reposts_'.$url; if (config('app.env') != 'local') { $this->reposts = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getRepost($webmentions); }); } $key = 'article_comments_'.$url; if (config('app.env') != 'local') { $this->comments = \Illuminate\Support\Facades\Cache::remember($key, 21600, function () use ($webmentions) { return $this->getComments($webmentions); }); } ``` Tambien hice 3 metodos que se encargan de cada respuesta otorgada por las webmentions ```php private function getComments($webmentions) { return $webmentions ->filter(fn (array $entry): bool => in_array($entry['wm-property'], ['mention-of', 'in-reply-to'])) ->reject(fn (array $entry): bool => empty($entry['content']['text'])); } private function getLikes($webmentions) { return $webmentions ->filter(fn (array $entry): bool => $entry['wm-property'] === 'like-of'); } private function getRepost($webmentions) { return $webmentions->filter(function (array $entry): bool { if ($entry['wm-property'] === 'repost-of') { return true; } if ($entry['wm-property'] === 'mention-of') { return empty($entry['content']['text']); } return false; }); } ``` En líneas generales lo que hice fue ordenar un poco el componente inicial creado por Tom y lo adapté a como quería. Luego haré un post de como mostrar todo esto usando otro componente ;) --- ### Hablemos sobre alpinejs - URL: https://www.angelcruz.dev/post/hablemos-sobre-alpinejs - Markdown: https://www.angelcruz.dev/post/hablemos-sobre-alpinejs.md - Categoría: JavaScript - Fecha: 2021-01-04 - Excerpt: Alpine.js: framework JavaScript ligero con reactividad de Vue/React. Ideal para comportamiento dinámico sin el peso de frameworks grandes. Guía completa. --- title: "Hablemos sobre alpinejs" excerpt: "Alpine.js: framework JavaScript ligero con reactividad de Vue/React. Ideal para comportamiento dinámico sin el peso de frameworks grandes. Guía completa." date: "2021-01-04T22:14:05.000Z" category: "JavaScript" seo_title: "Alpine.js: reactividad ligera sin el peso de Vue o React" seo_description: "Alpine.js ofrece reactividad en el DOM usando directivas como x-data, x-init y x-text. Tutorial práctico creando un widget del clima con la API de OpenWeatherMap." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![](https://alpinejs.dev/alpine_long.svg) Alpine nos ofrece 14 directivas y 6 propiedades mágicas que puedes conocer leyendo su [documentación](https://github.com/alpinejs/alpine/blob/master/README.es.md). ### Hagamos un pequeño ejemplo para entender mejor qué es alpinejs Bueno, pongamos manos a la obra y trabajamos en un widget para el clima que se verá más o menos como esto: ![proyecto alpinejs](https://i.ibb.co/k8Bxmrt/proyecto-alpine-js.png) template original de [iaminos](https://tailwindcomponents.com/component/weather-ui-component). Para hacer este proyecto necesitamos conocer sobre: - template strings - algunas directivas de alpine: - x-data: Declara un nuevo scope del componente. - x-init: Ejecuta una expresión cuando un componente se inicializa. - x-text: Actualiza el innerText del elemento. - tener una llave api de open weather map. Asumiendo que bajaron el template vamos a irlo modificando poco a poco. En el `` vamos a incluir esta estiqueta javascript: ```html ``` Ya con esto tendremos alpine inicializado. Ahora vamos a crear una etiqueta `script` y vamos a incluir lo siguiente: ```js function temp() { return { temp: {}, init() { // todo } } } ``` Ahora lo que debemos hacer es declarar el scope e inicializarlo de la siguiente forma: ```html
``` > NOTA: voy a remover todas las clases CSS y dejar las directivas de alpine Aquí lo que indicamos es que el scope de este componente será la función `temp()` y que solo estará disponible para todo aquello que esté dentro de las etiquetas div donde fue declarado. Teniendo en cuenta esto pasemos a terminar de armar nuestro widget. ```html
n/a
n/a
n/a
☝️ n/a
👇 n/a
Wind
n/a
Humidity
n/a
Feels like
n/a
``` Y nuestra función javascript pasaría a quedar de esta forma: ```javascript function temp() { return { temp: {}, init() { fetch('http://api.openweathermap.org/data/2.5/weather?q=Montevideo,UY&appid=&units=metric') .then(response => response.json()) .then(response => { this.temp = response }) } } } ``` Si no tienes ganas de codear y solo quieres mirar [pasa por aquí](https://codepen.io/abr4xas/pen/qBaRROM), solo vas a necesitar tu clave api y listo. --- ### Simple blog, el paquete que hice para gestionar mi blog. - URL: https://www.angelcruz.dev/post/simple-blog-el-paquete-que-hice-para-gestionar-mi-blog - Markdown: https://www.angelcruz.dev/post/simple-blog-el-paquete-que-hice-para-gestionar-mi-blog.md - Categoría: Laravel - Fecha: 2021-01-03 - Excerpt: Simple blog es un paquete para laravel que me permite gestionar mi blog de una forma más sencilla y sin complicaciones. --- title: "Simple blog, el paquete que hice para gestionar mi blog." excerpt: "Simple blog es un paquete para laravel que me permite gestionar mi blog de una forma más sencilla y sin complicaciones." date: "2021-01-03T19:57:29.000Z" category: "Laravel" seo_title: "simple-blog: paquete Laravel para gestionar un blog con markdown" seo_description: "simple-blog es un paquete Composer para Laravel que gestiona posts en markdown con relación polimórfica. Instalación con una migración y el trait HasArticle sin panel de administración." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- ## Instalación Usando composer ```bash composer require abr4xas/simple-blog ``` Publicamos las cosas que necesitamos para hacerlo funcionar: ```bash php artisan vendor:publish --provider="Abr4xas\SimpleBlog\SimpleBlogServiceProvider" --tag="simpleblog-migrations" php artisan migrate ``` ## Cómo se usa...? - Los post se escriben en markdown - Este paquete usa una relación polimórfica para asociar el modelo de Items con el modelo de su elección, lo único que tiene que hacer es agregar el siguiente trait: `Abr4xas\SimpleBlog\Traits\HasArticle` al modelo que quieras usar. - No contempla un panel de administración (por el momento). Los invito a conocer más del paquete en [https://github.com/abr4xas/simple-blog](https://github.com/abr4xas/simple-blog). --- ### Copiar archivos estáticos con scp usando github actions - URL: https://www.angelcruz.dev/post/copiar-archivos-estaticos-con-scp-usando-github-actions - Markdown: https://www.angelcruz.dev/post/copiar-archivos-estaticos-con-scp-usando-github-actions.md - Categoría: DevOps - Fecha: 2020-08-22 - Excerpt: Usemos scp usando github actions para publicar contenido estático creado por algun generador de sitios estáticos como pelican, jigsaw o gatsby a nuestro servidor. --- title: "Copiar archivos estáticos con scp usando github actions" excerpt: "Usemos scp usando github actions para publicar contenido estático creado por algun generador de sitios estáticos como pelican, jigsaw o gatsby a nuestro servidor." date: "2020-08-22T05:16:23.000Z" category: "DevOps" seo_title: "Publicar sitios estáticos con SCP y GitHub Actions automáticamente" seo_description: "Automatiza el despliegue de sitios estáticos con SCP usando GitHub Actions. Workflow con secretos SSH para publicar en tu servidor en cada push a la rama master." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- La manera facíl, sencilla y rápido es con este workflow: ```bash name: Deploy on: push: branches: - master # Change this to your default branch jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Copy file via scp uses: appleboy/scp-action@master env: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} PORT: ${{ secrets.PORT }} KEY: ${{ secrets.SSHKEY }} PASSPHRASE: ${{secrets.PASSPHRASE}} with: source: "." target: ${{ secrets.TARGET }} ``` Hay que crear [variables de entornos secretas](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets) para los valores que vamos a usar: - HOST - USERNAME - PORT - SSHKEY - PASSPHRASE Este workflow debe ir en la siguiente ruta: `.github/workflows/publish.yml` y listo, al hacer esto cada vez que hagan un push a su rama master se va a publicar automáticamente en su servidor sin problemas. Nota: Es recomendable tener una SSHKEY dedicada a esta labor únicamente y que no contenga PASSPHRASE, de tenerla tendrán que agregarla en las secrets para que el script pueda funcionar. --- ### Directiva para VUE para mostrar errores de validación con Laravel - URL: https://www.angelcruz.dev/post/directiva-para-vue-para-mostrar-errores-de-validacion-con-laravel - Markdown: https://www.angelcruz.dev/post/directiva-para-vue-para-mostrar-errores-de-validacion-con-laravel.md - Categoría: Laravel - Fecha: 2020-05-28 - Excerpt: Directiva para VUE para mostrar errores de validación con Laravel --- title: "Directiva para VUE para mostrar errores de validación con Laravel" excerpt: "Directiva para VUE para mostrar errores de validación con Laravel" date: "2020-05-28T16:58:30.000Z" category: "Laravel" seo_title: "Directiva Vue para mostrar errores de validación de Laravel" seo_description: "Muestra errores de validación de Laravel en Vue con la directiva v-has-error del paquete vue-has-error-laravel. Instalación con npm e integración en formularios con Axios en minutos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Aquí el como usarla. ### instalación NPM: ```bash npm i vue-has-error-laravel ``` Requerirlo en su archivo de componente vue.js: ```javascript import VueHasErrorLaravel from 'vue-has-error-laravel' Vue.use(VueHasErrorLaravel) ``` ### instrucciones de uso ```javascript ``` Agrega `v-has-error` el attribute a tu elemento: ```html
``` ### Autor Leonardo Manuel Alvarez ### license MIT [Fuente](https://github.com/alvarez25leo/vue-has-error-laravel) --- ### password_hash en PHP: Cómo Hashear Contraseñas con bcrypt - URL: https://www.angelcruz.dev/post/hashing-passwords-con-php - Markdown: https://www.angelcruz.dev/post/hashing-passwords-con-php.md - Categoría: PHP - Fecha: 2020-05-14 - Excerpt: Cómo usar password_hash en PHP para proteger contraseñas: comparativa con md5/sha1, opciones de cost y salt, y por qué bcrypt sigue siendo el default seguro. --- title: "password_hash en PHP: Cómo Hashear Contraseñas con bcrypt" excerpt: "Cómo usar password_hash en PHP para proteger contraseñas: comparativa con md5/sha1, opciones de cost y salt, y por qué bcrypt sigue siendo el default seguro." date: "2020-05-14T08:13:57.000Z" category: "PHP" seo_title: "password_hash en PHP: Guía con bcrypt, salt y cost (Segura)" seo_description: "Usa password_hash con PASSWORD_BCRYPT en PHP para proteger contraseñas. Comparativa con md5/sha1, opciones de cost, salt automático y ejemplos de código reales." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" --- ![Y U NO HASH PASSWORD?](https://www.drupal.org/files/project-images/_DSC1711.jpg) Una de las formas más fáciles de crear un hash para las contraseñas usando php es `md5` podemos ver en este ejemplo cómo implementarlo: ```php md5('password'); ``` Esto nos dara como resultado algo asi: ```php 5f4dcc3b5aa765d61d8327deb882cf99 ``` Pero, realmente esto no es muy efectivo. Usando `password_hash` Usando la documentación de php nos dice que para usar esta función debemos hacer lo siguiente: ```php string password_hash ( string $password , integer $algo [, array $options] ) ``` ### Algoritmos soportados - PASSWORD\_DEFAULT - PASSWORD\_BCRYPT ### Opciones soportadas: - Salt - Cost ## Como usar `password_hash` Usando `PASSWORD_DEFAULT` ```php password_hash('password', PASSWORD_DEFAULT); ``` Esto nos dara como resultado algo asi: ```php $2y$10$hdbF9lyKu5sWphXMtDB46eAO0RNYYlSTufwqIvmC6AC7xhC27s2w. ``` Usando `PASSWORD_BCRYPT` ```php echo password_hash('password', PASSWORD_BCRYPT); ``` Esto nos dara como resultado algo asi: ```php $2y$10$U4cpa/joKALEIIZT72K.Ze7s4S2eXTxee4NpnoOgZEZuqwCo1ZCQK ``` Vamos un poco más allá... Tomando en cuenta lo que nos dice la documentación aún podemos pasarle un tercer parámetro que será un `array` con ciertas caracteristicas: ```php $options = [ 'cost' => 11, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM), ]; ``` Y nuestra pequeña función pasa a ser esto: ```php echo password_hash('password', PASSWORD_BCRYPT, $options); ``` Esto nos dara como resultado algo asi: ```php $2y$11$7.RPImGZC4gN/Tf5sInLGeOkqtUirnway.gOEacAdmlOFk.ipPL4m ``` Hay que tener en cuenta que la opción `salt` ya se encuentra `deprecated` en la version 7.0.0 de php por lo cual, recomiendan usar el `salt` que se genera por defecto. Como podemos ver en los resultados de cada ejemplo hay ciertas cosas que siempre van a ser iguales (en el caso que no cambiemos el valor de `cost`) y esto lo podemos ver en la siguiente imagen: ![Ejemplo de hash password con php](http://php.net/manual/en/images/2a34c7f2e658f6ae74f3869f2aa5886f-crypt-text-rendered.svg) Espero que esto les sea de utilidad. Lecturas recomendadas: [http://php.net/manual/en/function.password-hash.php](http://php.net/manual/en/function.password-hash.php). --- ### Cúal es la diferencia entre where y having en mysql? - URL: https://www.angelcruz.dev/post/cual-es-la-diferencia-entre-where-y-having-en-mysql - Markdown: https://www.angelcruz.dev/post/cual-es-la-diferencia-entre-where-y-having-en-mysql.md - Categoría: Bases de Datos - Fecha: 2020-04-17 - Excerpt: Probablemente es algo que no sabías y te puede servir así como a mi. --- title: "Cúal es la diferencia entre where y having en mysql?" excerpt: "Probablemente es algo que no sabías y te puede servir así como a mi." date: "2020-04-17T05:16:03.000Z" category: "Bases de Datos" seo_title: "WHERE vs HAVING en MySQL: diferencias clave con ejemplos" seo_description: "WHERE filtra filas antes del SELECT y no permite alias; HAVING filtra resultados después y sí permite alias y funciones agregadas. Diferencias explicadas con EXPLAIN y ejemplos SQL." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Vayamos directo al grano: Digamos que tenemos esta tabla: ``` CREATE TABLE `table` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `value` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `value` (`value`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ``` Donde tenemos 10 rows, ambos con `id` y `value` que van de 1 a 10: ``` INSERT INTO `table`(`id`, `value`) VALUES (1, 1),(2, 2),(3, 3),(4, 4),(5, 5),(6, 6),(7, 7),(8, 8),(9, 9),(10, 10); ``` Si hacemos las siguientes querys: ``` SELECT `value` v FROM `table` WHERE `value`>5; -- Get 5 rows SELECT `value` v FROM `table` HAVING `value`>5; -- Get 5 rows ``` Se obtienen exactamente los mismos resultados y como pueden ver, la clausula `having` puede trabajar sin la clausula `GROUP BY` ### Aquí la diferencia Si hacemos esto: ``` SELECT `value` v FROM `table` WHERE `v`>5; ``` Vamos a obtener: ``` Error #1054 - Unknown column 'v' in 'where clause' ``` En cambio: ``` SELECT `value` v FROM `table` HAVING `v`>5; -- Get 5 rows ``` La clausula `WHERE` permite una condición para usar cualquier columna de una tabla pero no permite usar alias o funciones agregadas. La clausula `HAVING` permite hacer condiciones para usar una columna, alias o una funcion agregada. Esto se debe a que la clausula `WHERE` filtra la data antes de ejecutar el `select` pero `HAVING` filtra los resultados despues de hacer el `select` Usemos `EXPLAIN` para entender las diferencias: ``` EXPLAIN SELECT `value` v FROM `table` WHERE `value`>5; +----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+ | 1 | SIMPLE | table | range | value | value | 4 | NULL | 5 | Using where; Using index | +----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+ EXPLAIN SELECT `value` v FROM `table` having `value`>5; +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+ | 1 | SIMPLE | table | index | NULL | value | 4 | NULL | 10 | Using index | +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+ ``` Cualquiera de los dos (`WHERE` o `HAVING`) usan `index`pero las rows son diferentes. [Fuente](https://stackoverflow.com/a/18710763/1814613). --- ### Instalar Robo 3T (formerly Robomongo) en Ubuntu 18.04 - URL: https://www.angelcruz.dev/post/instalar-robo-3t-formerly-robomongo-en-ubuntu-1804 - Markdown: https://www.angelcruz.dev/post/instalar-robo-3t-formerly-robomongo-en-ubuntu-1804.md - Categoría: Bases de Datos - Fecha: 2020-04-16 - Excerpt: Robo3T, anteriormente conocido como RobMongo, es una de las mejores herramientas GUI para administrar y consultar la base de datos MongoDB. --- title: "Instalar Robo 3T (formerly Robomongo) en Ubuntu 18.04" excerpt: "Robo3T, anteriormente conocido como RobMongo, es una de las mejores herramientas GUI para administrar y consultar la base de datos MongoDB." date: "2020-04-16T17:35:33.000Z" category: "Bases de Datos" seo_title: "Instalar Robo 3T para MongoDB en Ubuntu 18.04 paso a paso" seo_description: "Instala Robo 3T (antes Robomongo) en Ubuntu 18.04 usando la terminal. Guía completa con wget, tar, chmod y configuración del icono de escritorio en menos de 10 minutos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![](https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSxsQjcDgKC00nVvGbUkDjbWgK1dgwnBd98ToElFH5KuXEubyBw&usqp=CAU) El proceso de instalación es muy sencillo solo hay que hacer uso de la terminal: Descargamos el paquete desde [Robo3t](https://robomongo.org/download "https://robomongo.org/download") o podemos usar `wget` ```shell wget -c https://download-test.robomongo.org/linux/robo3t-1.3.1-linux-x86_64-7419c406.tar.gz ``` Descomprimes el archivo ```shell tar -xvzf robo3t-1.3.1-linux-x86_64-7419c406.tar.gz ``` Hay que hacer una carpeta en `usr/local/bin` ```shell sudo mkdir /usr/local/bin/robo3t ``` Luego movemos a `usr/local/bin` ```shell sudo mv robo3t-1.3.1-linux-x86_64-7419c406/* /usr/local/bin/robo3t ``` Ya casi por último nos movemos a `cd /usr/local/bin/robo3t/bin` Ahora le damos permisos al binario para que se ejecute `chmod` ```shell sudo chmod +x robo3t ./robo3t ``` Ahora podemos iniciar la aplicación con esto: ```shell ./robo3t ``` Ahora podemos buscar un [icon](https://www.google.com/search?q=robo3t+icon+png&tbm=isch&source=iu&ictx=1&fir=Lh5FTCRLPKZyvM%253A%252CT0TupOjHzw6HKM%252C_&usg=AI4_-kRUiahKne4RFzDIMMulD1ZHJZNzAA&sa=X&ved=2ahUKEwiXtenUqbjgAhUBUhUIHfvEACQQ9QEwAHoECAUQBA#imgrc=Lh5FTCRLPKZyvM: "https://www.google.com/search?q=robo3t+icon+png&tbm=isch&source=iu&ictx=1&fir=Lh5FTCRLPKZyvM%253A%252CT0TupOjHzw6HKM%252C_&usg=AI4_-kRUiahKne4RFzDIMMulD1ZHJZNzAA&sa=X&ved=2ahUKEwiXtenUqbjgAhUBUhUIHfvEACQQ9QEwAHoECAUQBA#imgrc=Lh5FTCRLPKZyvM:") para Robo3t que usaremos luego. Despues de bajar el icono que más nos guste lo mandamos a `/bin` con el nombre `icon.png` de la siguiente forma: `mv icon.png /usr/local/bin/robo3t/bin` Para hacer un `desktop icon` for `Robo3t`, tenemos que hacer un archivo nuevo en `usr/share/applications` ```shell sudo nano /usr/share/applications/robo3t.desktop ``` Colocamos esto y salvamos el archivo: ```shell [Desktop Entry] Encoding=UTF-8 Type=Application Name=Robo3t Icon=/usr/local/bin/robo3t/bin/icon.png Exec="/usr/local/bin/robo3t/bin/robo3t" Comment=Robo3t Categories=Development; Terminal=false StartupNotify=true ``` [Reference](https://www.dotnetjalps.com/2018/03/install-robo3t-robmongo-ubuntu.html "https://www.dotnetjalps.com/2018/03/install-robo3t-robmongo-ubuntu.html") --- ### Crear un búscador con Laravel y spatie/laravel-searchable. - URL: https://www.angelcruz.dev/post/crear-un-buscador-con-laravel-y-spatielaravel-searchable - Markdown: https://www.angelcruz.dev/post/crear-un-buscador-con-laravel-y-spatielaravel-searchable.md - Categoría: Laravel - Fecha: 2020-03-19 - Excerpt: Si, hay muchos tutoriales en internet sobre esto pero esta vez será algo rápido de implementar ;) --- title: "Crear un búscador con Laravel y spatie/laravel-searchable." excerpt: "Si, hay muchos tutoriales en internet sobre esto pero esta vez será algo rápido de implementar ;)" date: "2020-03-19T18:08:14.000Z" category: "Laravel" seo_title: "Crear un buscador en Laravel con spatie/laravel-searchable" seo_description: "Implementa un buscador en Laravel sin Algolia usando el paquete spatie/laravel-searchable. Configura la interfaz Searchable en tu modelo y crea un endpoint de búsqueda en minutos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Ok, recientemente he estado trabajando en una aplicación que llegó a un punto que necesitaba un buscador. Claro, existen cosas como [Laravel Scout](https://laravel.com/docs/master/scout) pero por ser un MVP el proyecto no cuenta con un budget que permita invertir en algolia. Entonces, buscando siempre en los paquetes creados por la comunidad resaltan los de [spatie](https://spatie.be/open-source/packages), sus paquetes me han ayudado a resolver problemas de una forma mucho más rápida y eficiente. El paquete que necesite para esto es: ```shell composer require spatie/laravel-searchable ``` La configuración de este paquete es sencilla, solo requiere que implementemos la interfaz `Searchable` en el modelo a usar, de la siguiente forma: ```php title, route('front.post.show', $this->slug) ); } } ``` Aquí básicamente lo que estamos haciendo es crear una nueva instancia de `SearchResult` dónde le pasamos como parámetros: el modelo en sí mismo, el título y la url del recurso. Ahora creamos un controlador para poder tener acceso a la data desde nuestro frontend usando (por ejemplo) VueJS / ReactJS, Axios o Ajax. En mi caso, usé un [Single Action Controller](https://laravel.com/docs/5.7/controllers#single-action-controllers) de la siguiente forma: ```php registerModel(Post::class, ['title']) ->search($request->search); return response()->json($response); } } ``` Ya lo único que queda es crear una ruta: ```php Route::get('/search', 'Front\Search\SearchController') ->name('front.search'); ``` NOTA: este post es básicamente una traducción del readme del paquete `spatie/laravel-searchable`. Para más info, vayan al [repo](https://github.com/spatie/laravel-searchable). --- ### Arduino Uno con ¿javascript? - URL: https://www.angelcruz.dev/post/arduino-uno-con-javascript - Markdown: https://www.angelcruz.dev/post/arduino-uno-con-javascript.md - Categoría: JavaScript - Fecha: 2020-02-11 - Excerpt: Controla Arduino con JavaScript usando Johnny-Five. Tutorial paso a paso para hacer proyectos interesantes de forma sencilla y entretenida. --- title: "Arduino Uno con ¿javascript?" excerpt: "Controla Arduino con JavaScript usando Johnny-Five. Tutorial paso a paso para hacer proyectos interesantes de forma sencilla y entretenida." date: "2020-02-11T03:56:34.000Z" category: "JavaScript" seo_title: "Controlar Arduino con JavaScript usando Johnny-Five" seo_description: "Controla Arduino Uno con JavaScript y Node.js usando la librería Johnny-Five. Tutorial con ejemplos reales: parpadeo de LED y peticiones HTTP desde el microcontrolador." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Hace unos días tengo un arduino uno (después de mucho tiempo queriendo tenerlo xD) y bueno, he pasado el tiempo probando los sensores y recordando mi época de la universidad con los micro controladores xD El "combo" arduino que tengo: - ARDUINO UNO R3 - Sensor Infrarrojo Para Obstáculos Arduino Fc 51 - Zumbador Buzzer Arduino 5v - Llave Interruptor Basculante - Tcrt 5000 Sensor Reflectivo Infrarrojo - Sensor Ultrasonido Hysrf04 - Arduino, Puente HL293D - MicroServo Sg 90 180° - Sensor De Temperatura Y Humedad Dht11 - Modulo Rele Arduino 10a Ky-019 5 V 1 Canal - Sensor De Humedad - Fotoresistor Ldr 5528 5mm Después de hacer algunas pruebas con los sensores se me ocurrió buscar si existía algo para node js que permitiera controlar el arduino. > Johnny-Five es un proyecto para aficionados al desarrollo web que empiezan a jugar con Arduino o para los que ya lo hacen y buscan integrar algún prototipo a una aplicación web, corre bajo un servidor Nodejs y esta pensado para programar Arduino en Javascript con ayuda del ya famosa “Firmata”. ### Hola mundo Para este ejemplo necesitaremos: - Arduino pre cargado con Firmata y para esto tenemos que tener Arduino IDE instalado, la ruta para hacerlo es: `File > Examples > Firmata > StandardFirmata / StandardFirmataPlus`. - Nodejs con npm o yarn - johnny-five El ejemplo de la documentación de johnny-five es con un led ```javascript const {Board, Led} = require("johnny-five"); const board = new Board(); board.on("ready", () => { const led = new Led(13); led.blink(500); }); ``` El resultado es el siguiente: ![johnny-five led scene](http://johnny-five.io/img/led-scene-0.gif) En mi caso, no tengo un led (olvidé comprar xD) entonces lo que hice fue cambiar el led por un `console.log` ```javascript const {Board} = require("johnny-five"); const board = new Board(); board.on("ready", () => { console.log('Hello world from arduino uno') }); ``` Eso me retorna lo siguiente: ```bash → node hello >> Hello world from arduino uno ``` ![it's alive gif](https://media3.giphy.com/media/UqUJhrD0om73q/giphy.gif) Entonces, como ven esta funcionando... Ahora se me ocurre que podemos hacer algunas otras cosas quizás no tan complejas pero podemos aprovechar que estamos usando node para hacer algunas cosas como una petición http (por ejemplo) Para esta segunda parte del ejemplo vamos a necesitar `https` y nuestro script pasa a ser lo siguiente: ```javascript const { Board } = require('johnny-five') const https = require('https') const board = new Board({ port: '/dev/ttyUSB0' }) const options = { hostname: 'api.chucknorris.io', port: 443, path: '/jokes/random', method: 'GET' } board.on('ready', () => { const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => { let response = JSON.parse(d) console.log(response.value) }) }) req.on('error', (error) => { console.error(error) }) req.end() }) ``` En esta oportunidad haremos una petición al api de chucknorris.io para obtener un fact sobre Chuck Norris, si ejecutamos nuevamente nuestro archivo vamos a obtener algo como esto: ```bash → node hello >> statusCode: 200 Chuck Norris can cremate you instantly with a mean-ass death stare. ``` ¡Funciona! Visita la web de johnny-five.io para conocer más. --- ### Integrando "Invisible reCAPTCHA" de Google de forma fácil en Laravel - URL: https://www.angelcruz.dev/post/integrando-invisible-recaptcha-de-google-de-forma-facil-en-laravel - Markdown: https://www.angelcruz.dev/post/integrando-invisible-recaptcha-de-google-de-forma-facil-en-laravel.md - Categoría: Laravel - Fecha: 2020-01-18 - Excerpt: Vamos a integrar "Invisible reCAPTCHA" de Google en Laravel en menos de 5 minutos. --- title: "Integrando \"Invisible reCAPTCHA\" de Google de forma fácil en Laravel" excerpt: "Vamos a integrar \"Invisible reCAPTCHA\" de Google en Laravel en menos de 5 minutos." date: "2020-01-18T02:43:01.000Z" category: "Laravel" seo_title: "Integrar reCAPTCHA v3 Invisible en Laravel sin paquetes" seo_description: "Implementa Google reCAPTCHA v3 en Laravel usando una clase personalizada con Guzzle y directivas Blade. Valida el score del usuario en el controlador en menos de 5 minutos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- Ok, lo primero que hay que hacer es crear las llaves que vamos a usar para ello debemos is a: [https://www.google.com/recaptcha/admin/create](https://www.google.com/recaptcha/admin/create) recuerden que debemos seleccionar **reCAPTCHA v3** Bien, ya tenemos las llaves ahora necesitamos hacer es crear una clase que podemos llamar `CustomGoogleRecaptcha` y tendrá el siguiente namespace `App\Helpers` (pueden adaptarlo según sus necesidades). Esta clase va a contener lo siguiente: ```php client = new Client([ 'base_uri' => self::CAPTCHA_URL, 'timeout' => self::TIMEOUT, ]); } /** * validateCaptcha * * @param [type] $recaptcha * @return void */ public function validateCaptcha($recaptcha) { $captchaData = [ 'headers' => [ 'content-type' => 'application/x-www-form-urlencoded', 'accept' => 'application/json', 'cache-control' => 'no-cache' ], 'form_params' => [ 'secret' => config('google.recaptcha_secret_key'), 'response' => $recaptcha ] ]; $request = $this->client->request('POST', 'siteverify', $captchaData); $body = $request->getBody()->getContents(); $response = json_decode($body, true); // score over 0.5 human return $response['score'] >= '0.5' ? true : false; } } ``` Luego en nuestro `AppServiceProvider` en el método `register` colocamos esto ```php $this->app->bind(ReCaptcha::class, function () { return new ReCaptcha; }); ``` Y en el método `boot` colocamos lo siguiente: ```php Blade::directive('rederRecaptchaJs', function ($key, $action = 'contact_form' ) { return ' '; }); Blade::directive('rederRecaptcha', function ($key) { return '
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
'; }); ``` Para usarlo en las vistas como una directiva normal de blade, en este caso sería: ```shell @rederRecaptchaJs() @rederRecaptcha ``` Y ya, para finalizar hacemos lo siguiente en el controlador donde se necesite usar este recaptcha: ```php public $recaptcha; public function __construct(ReCaptcha $recaptcha) { $this->recaptcha = $recaptcha; } /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function __invoke(ContactFormRequest $request) { if ($this->recaptcha->validateCaptcha($request->recaptcha)) { // } // } ``` Aquí la validación que se hace es el score del usuario, donde un valor menor a 0.5 es considerado un bot. Espero que les sirva. --- ### Ordenar por sku con woocommerce - URL: https://www.angelcruz.dev/post/ordenar-por-sku-con-woocommerce - Markdown: https://www.angelcruz.dev/post/ordenar-por-sku-con-woocommerce.md - Categoría: WordPress - Fecha: 2019-12-05 - Excerpt: Recientemente he tenido la necesidad de agregar un filtro adicional a la hora de ordenar el listado de productos usando woocommerce, haciendo una búsqueda por papá Google encontré un gist en github que resolvió el requerimiento. --- title: "Ordenar por sku con woocommerce" excerpt: "Recientemente he tenido la necesidad de agregar un filtro adicional a la hora de ordenar el listado de productos usando woocommerce, haciendo una búsqueda por papá Google encontré un gist en github que resolvió el requerimiento." date: "2019-12-05T16:42:40.000Z" category: "WordPress" seo_title: "Ordenar productos por SKU en WooCommerce con un filtro PHP" seo_description: "Agrega la opción de ordenar productos por SKU en WooCommerce usando los filtros woocommerce_get_catalog_ordering_args y woocommerce_catalog_orderby. Solución en menos de 20 líneas de PHP." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Algunas veces es necesario agregar funcionalidades adicionales a los proyectos porque así lo requiere el modelo de negocio de nuestro proyecto. Este es el caso de ordenar por sku los productos de una tienda con woocommerce ## Que es sku Stock-keeping unit o SKU, sin traducción literal al español podría denominarse «código de artículo» o «número de referencia». Asignado a un elemento para poder identificarlo en el inventario físico y/o financiero. El SKU representa para una empresa la unidad mínima de un producto que puede ser vendida, comprada, o gestionada al inventario. Aplicada a la distribución o producción, el SKU se puede utilizar para seguir transacciones y movimientos de inventario, analizar patrones de compra-venta, seguimiento de precios y fluctuaciones en el inventario. https://es.wikipedia.org/wiki/Stock-keeping_unit Una de las cosas super interesantes que posee wordpress es el uso de filtros para poder agregar o cambiar una funcionalidad de algo. En esta oportunidad, lo que necesitaba hacer era agregar una funcionalidad extra a `woocommerce_defaultcatalog_orderby` ```php /** * Adds the ability to sort products in the shop based on the SKU * Can be combined with tips here to display the SKU on the shop page: https://www.skyverge.com/blog/add-information-to-woocommerce-shop-page/ * * @param array $args the sorting args * @return array updated args */ function svaddsku_sorting( $args ) { $orderbyvalue = isset( $GET['orderby'] ) ? wcclean( $GET['orderby'] ) : applyfilters( 'woocommercedefaultcatalogorderby', getoption( 'woocommercedefaultcatalogorderby' ) ); if ( 'sku' == $orderby_value ) { $args['orderby'] = 'meta_value'; $args['order'] = 'asc'; // lists SKUs alphabetically 0-9, a-z; change to desc for reverse alphabetical $args['metakey'] = 'sku'; } return $args; } addfilter( 'woocommercegetcatalogorderingargs', 'svaddskusorting' ); /** * Add the option to the orderby dropdown. * * @param array $sortby the sortby options * @return array updated sortby */ function svskusorting_orderby( $sortby ) { // Change text above as desired; this shows in the sorting dropdown $sortby['sku'] = __( 'Sort by SKU', 'textdomain' ); return $sortby; } addfilter( 'woocommercecatalogorderby', 'svskusortingorderby' ); addfilter( 'woocommercedefaultcatalogorderbyoptions', 'svskusortingorderby' ); ``` Hagan clic [aquí](https://gist.github.com/bekarice/1883b7e678ec89cc8f4d#file-wc-sku-sorting-php) para ver el script original. --- ### Propuesta security.txt - URL: https://www.angelcruz.dev/post/propuesta-securitytxt - Markdown: https://www.angelcruz.dev/post/propuesta-securitytxt.md - Categoría: Web - Fecha: 2019-11-09 - Excerpt: Una propuesta sobre un standard que permite a los websites que permite definir políticas de seguridad --- title: "Propuesta security.txt" excerpt: "Una propuesta sobre un standard que permite a los websites que permite definir políticas de seguridad" date: "2019-11-09T04:19:57.000Z" category: "Web" seo_title: "security.txt: estándar para divulgar vulnerabilidades web" seo_description: "security.txt es un estándar propuesto para que organizaciones publiquen su política de divulgación de vulnerabilidades. Facilita que investigadores reporten problemas de forma segura y responsable." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![securitytxt](https://user-images.githubusercontent.com/18099289/77641182-eb620c80-6f5b-11ea-9be5-02639c94b141.png) Cuando los riesgos de seguridad en los servicios web son descubiertos por investigadores de seguridad independientes que entienden la gravedad del riesgo, a menudo carecen de los canales para divulgarlos adecuadamente. Como resultado, los problemas de seguridad pueden quedar sin informar. security.txt define un estándar para ayudar a las organizaciones a definir el proceso para que los investigadores de seguridad revelan vulnerabilidades de seguridad de forma segura. Para más información visita el proyecto: [https://securitytxt.org/](https://securitytxt.org/) --- ### Migrar de Joomla a WordPress y no morir en el intento - URL: https://www.angelcruz.dev/post/migrar-de-joomla-a-wordpress-y-no-morir-en-el-intento - Markdown: https://www.angelcruz.dev/post/migrar-de-joomla-a-wordpress-y-no-morir-en-el-intento.md - Categoría: WordPress - Fecha: 2019-10-19 - Excerpt: Existe un plugin que permite migrar todo el contenido de una instalación Joomla a Wordpress y no es complicado de usar. --- title: "Migrar de Joomla a WordPress y no morir en el intento" excerpt: "Existe un plugin que permite migrar todo el contenido de una instalación Joomla a Wordpress y no es complicado de usar." date: "2019-10-19T02:15:44.000Z" category: "WordPress" seo_title: "Migrar de Joomla a WordPress sin perder contenido" seo_description: "Migra categorías, posts, etiquetas e imágenes de Joomla 1.5 a 3.3 a WordPress usando el plugin FG Joomla to WordPress. Compatible con instalaciones multisite y bases de datos grandes." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Un plugin para migrar categorías, mensajes, etiquetas, imágenes y otros archivos multimedia de Joomla a WordPress. Se ha probado con las versiones de Joomla 1.5, 1.6, 1.7, 2.5, 3.0, 3.1, 3.2 y 3.3 y WordPress 4.0 en bases de datos grandes (000+ 72 mensajes). Es compatible con las instalaciones de múltiples sitios. ## Instalación: - Instalar el plugin desde el panel de administración => Plugins menu => Add New => Upload => Seleccionar el archivo zip => Instalar ahora O simplemente usar el formulario de búsqueda y colocar: FG Joomla to WordPress => Instalar ahora - Activar el plugin - Iniciar la herramienta de importación => Import => Joomla (FG) - Hay que configurar los ajustes del plugin para que podamos importar lo que necesitamos, estos datos los podemos encontrar en el archivo configuration.php de Joomla donde, vamos a necesitar los siguientes valores: ```php Hostname = $host Port = 3306 (standard MySQL port) Database = $db Username = $user Password = $password Joomla Table Prefix = $dbprefix ``` Para obtener más información, visita esta pagina: https://wordpress.org/plugins/fg-joomla-to-wordpress/ --- ### JSON feed - URL: https://www.angelcruz.dev/post/json-feed - Markdown: https://www.angelcruz.dev/post/json-feed.md - Categoría: Web - Fecha: 2019-09-28 - Excerpt: JSON feed es un nuevo estándar para formalizar un feed RSS basado en JSON que pretende simplificar la creación de feeds eliminando el estándar XML. La implementación de un feed para su sitio es simple y la especificación es sorprendentemente clara. --- title: "JSON feed" excerpt: "JSON feed es un nuevo estándar para formalizar un feed RSS basado en JSON que pretende simplificar la creación de feeds eliminando el estándar XML. La implementación de un feed para su sitio es simple y la especificación es sorprendentemente clara." date: "2019-09-28T14:39:17.000Z" category: "Web" seo_title: "JSON Feed: implementar un feed RSS en JSON con Laravel" seo_description: "JSON Feed es el estándar RSS basado en JSON que simplifica la creación de feeds eliminando XML. Tutorial con Laravel: configura los campos base e itera tus posts en pocas líneas." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ## La estructura de un feed Un feed JSON comienza con alguna información en la parte superior: dice de dónde proviene el feed, y puede decir quién lo creó, etc. Después de eso, hay una serie de objetos, elementos, que describen cada objeto en la lista. ```json { "version": "https://jsonfeed.org/version/1", "title": "My Example Feed", "homepageurl": "https://example.org/", "feed_url": "https://example.org/feed.json", "items": [ { "id": "2", "content_text": "This is a second item.", "url": "https://example.org/second-item" }, { "id": "1", "content_html": "

Hello, world!

", "url": "https://example.org/initial-post" } ] } ``` Ejemplo rápido con laravel: obtener la lista de post ```php $posts = Posts::latest(); ``` Configura los datos básicos para el JSON Feed La especificación de JSON Feed tiene algunos campos de nivel superior opcionales, como título, URL de fuente, ícono de sitio y más. Estos campos no son dinámicos por lo que hay que agregarlos a un arreglo de forma manual: ```php $data = [ 'version' => 'https://jsonfeed.org/version/1', 'title' => 'El nombre de tu feed', 'homepageurl' => 'https://domain.tld', 'feed_url' => 'https://domain.tld/feed/json', 'icon' => 'https://domain.tld/apple-touch-icon.png', 'favicon' => 'https://domain.tld/apple-touch-icon.png', 'items' => [], ]; ``` Agregando los items al feed Este punto es lo más sencillo del mundo, únicamente hay que hacer: ```php $post) { $data['items'][$key] = [ 'id' => $post->id, 'title' => $post->title, 'url' => 'https://domain.tld/' . $post->uri, // o la ruta de los post 'image' => $post->featured_image, 'content_html' => $post->content, 'datepublished' => $post->created_at->tz('UTC')->toRfc3339String(), 'datemodified' => $post->updated_at->tz('UTC')->toRfc3339String(), 'author' => [ 'name' => $post->user->name ], ]; } ``` El resultado final Nuestro método quedaría de esta forma: ```php 'https://jsonfeed.org/version/1', 'title' => 'El nombre de tu feed', 'homepageurl' => 'https://domain.tld', 'feed_url' => 'https://domain.tld/feed/json', 'icon' => 'https://domain.tld/apple-touch-icon.png', 'favicon' => 'https://domain.tld/apple-touch-icon.png', 'items' => [], ]; foreach ($posts as $key => $post) { $data['items'][$key] = [ 'id' => $post->id, 'title' => $post->title, 'url' => 'https://domain.tld/' . $post->uri, // o la ruta de los post 'image' => $post->featured_image, 'content_html' => $post->content, 'datepublished' => $post->created_at->tz('UTC')->toRfc3339String(), 'datemodified' => $post->updated_at->tz('UTC')->toRfc3339String(), 'author' => [ 'name' => $post->user->name ], ]; } return $data; } } ``` --- ### Tipos TEXT en MySQL: TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT - URL: https://www.angelcruz.dev/post/tamanos-maximos-de-almacenamiento-de-text-tinytext-mediumlong-text - Markdown: https://www.angelcruz.dev/post/tamanos-maximos-de-almacenamiento-de-text-tinytext-mediumlong-text.md - Categoría: Bases de Datos - Fecha: 2019-09-21 - Excerpt: El tamaño máximo de cada tipo de texto en MySQL —TINYTEXT, TEXT, MEDIUMTEXT y LONGTEXT— en bytes y en caracteres, sus diferencias, y cómo elegir el correcto sin tener que migrar la tabla después. --- title: "Tipos TEXT en MySQL: TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT" excerpt: "El tamaño máximo de cada tipo de texto en MySQL —TINYTEXT, TEXT, MEDIUMTEXT y LONGTEXT— en bytes y en caracteres, sus diferencias, y cómo elegir el correcto sin tener que migrar la tabla después." date: "2019-09-21T20:01:45.000Z" lastModified: "2026-06-15" category: "Bases de Datos" tech_article: true seo_title: "TINYTEXT, TEXT, MEDIUMTEXT y LONGTEXT en MySQL: tamaños y usos" seo_description: "Tamaño máximo de los tipos TEXT de MySQL: TINYTEXT 255 B, TEXT 64 KiB, MEDIUMTEXT 16 MiB, LONGTEXT 4 GiB (4 294 967 295 bytes). Diferencias, bytes vs caracteres y cuándo usar cada uno." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Cuando diseño una tabla siempre llega la misma pregunta: para un campo de texto largo, ¿qué tipo uso? MySQL ofrece cuatro tipos de cadena pensados para esto —`TINYTEXT`, `TEXT`, `MEDIUMTEXT` y `LONGTEXT`— y la única diferencia real entre ellos es **cuántos bytes pueden almacenar**. Elegir el correcto desde el principio te evita migrar la tabla a mitad del proyecto. Aquí va la respuesta directa y, después, todo lo que conviene saber para no equivocarte. ## Tamaño máximo de cada tipo | Tipo | Tamaño máximo (bytes) | Equivalente | Prefijo de longitud | | --- | --- | --- | --- | | `TINYTEXT` | 255 (2⁸−1) | 255 B | 1 byte | | `TEXT` | 65 535 (2¹⁶−1) | 64 KiB | 2 bytes | | `MEDIUMTEXT` | 16 777 215 (2²⁴−1) | 16 MiB | 3 bytes | | `LONGTEXT` | 4 294 967 295 (2³²−1) | 4 GiB | 4 bytes | Ese `4 294 967 295` es el famoso límite de `LONGTEXT`: cuatro gigabytes por valor. Cada tipo guarda, además del contenido, un **prefijo de longitud** de 1 a 4 bytes que indica el tamaño del dato. > El tamaño en **bytes** es fijo, pero la cantidad de **caracteres** que entran depende de la codificación que uses. No es lo mismo `latin1` (1 byte por carácter) que `utf8mb4` (hasta 4 bytes por carácter). ## TINYTEXT en MySQL Hasta **255 bytes**. Es el más chico de la familia y rara vez lo elijo: para cadenas cortas casi siempre me conviene más un `VARCHAR(n)`, que se indexa completo y puede tener valor por defecto. `TINYTEXT` tiene sentido si quiero comportamiento de tipo TEXT (almacenamiento fuera de fila, sin `DEFAULT` literal) en un campo muy pequeño. ## TEXT en MySQL Hasta **65 535 bytes (64 KiB)**. Es el caballo de batalla: comentarios, descripciones, biografías, mensajes. Cubre la inmensa mayoría de los casos de "texto largo" sin desperdiciar nada. Si no estás seguro de cuál usar y el contenido es texto humano normal, `TEXT` es la apuesta segura. ## MEDIUMTEXT en MySQL Hasta **16 777 215 bytes (16 MiB)**. Lo uso cuando el contenido puede crecer de verdad: artículos completos en HTML o Markdown, payloads JSON medianos, el cuerpo serializado de un documento. Es el escalón que casi nadie recuerda que existe y que muchas veces es justo lo que hace falta antes de saltar a `LONGTEXT`. ## LONGTEXT en MySQL Hasta **4 294 967 295 bytes (4 GiB)**. Para datos realmente grandes: documentos enormes, dumps, contenido en base64, logs acumulados. Funciona, pero antes de llegar a ese extremo vale la pena preguntarse si una base de datos relacional es el lugar correcto para ese dato; muchas veces conviene guardar el archivo en un object storage (S3, Spaces) y dejar en MySQL solo la referencia. ## Comparativas rápidas ### LONGTEXT vs TEXT Misma semántica, distinto techo: `TEXT` llega a 64 KiB y `LONGTEXT` a 4 GiB. La regla que sigo es **usar el tipo más chico que cubra mi caso real**. Un `LONGTEXT` "por las dudas" no es gratis: complica los índices y puede inflar el uso de memoria en operaciones que materializan la columna (por ejemplo, ciertos `GROUP BY` o tablas temporales). ### MEDIUMTEXT vs TEXT `TEXT` son 64 KiB; `MEDIUMTEXT`, 16 MiB. Si tu contenido puede superar los 64 KiB —un artículo largo, un JSON que crece— `TEXT` te va a truncar el dato en silencio (o lanzar error según el modo SQL). Ahí `MEDIUMTEXT` es el salto natural. ### TEXT vs VARCHAR Esta es la duda más común. La diferencia práctica: - **`VARCHAR(n)`**: pensado para cadenas con una longitud máxima conocida (hasta 65 535 bytes, compartidos con el resto de la fila). Se guarda en la fila, se indexa completo y admite `DEFAULT`. Ideal para nombres, emails, slugs, títulos. - **`TEXT`** y familia: para contenido grande o sin tope claro. Se almacena fuera de la fila (en InnoDB), **no** admite `DEFAULT` literal antes de MySQL 8.0.13 y solo se indexa por prefijo. Si conozco el límite y es razonable, voy con `VARCHAR`. Si el texto es grande o impredecible, voy con `TEXT`/`MEDIUMTEXT`. ## Bytes vs caracteres: el matiz que casi nadie explica El máximo en bytes no cambia, pero los **caracteres** que entran sí, según la codificación. Con `utf8mb4` (la que deberías estar usando), un carácter ocupa hasta 4 bytes, así que el máximo en caracteres es el límite en bytes dividido entre 4: | Tipo | Máx. en `latin1` (1 B/car.) | Máx. en `utf8mb4` (≤4 B/car.) | | --- | --- | --- | | `TINYTEXT` | 255 | 63 | | `TEXT` | 65 535 | 16 383 | | `MEDIUMTEXT` | 16 777 215 | 4 194 303 | | `LONGTEXT` | 4 294 967 295 | 1 073 741 823 | Por eso un campo que "entraba justo" en `latin1` puede quedarte corto al migrar a `utf8mb4`. Es un detalle que se paga caro si aparece en producción. ## Detalles que importan en producción - **Overhead del prefijo**: cada valor agrega 1, 2, 3 o 4 bytes según el tipo, además del contenido. - **Índices por prefijo**: no puedes indexar una columna `TEXT` completa; tienes que indicar una longitud, por ejemplo `INDEX (columna(255))`. El máximo del prefijo depende del motor y el formato de fila (en InnoDB, 767 bytes por defecto, hasta 3072 con formato `DYNAMIC`/`COMPRESSED`). - **Almacenamiento fuera de fila**: InnoDB guarda los valores grandes en páginas de overflow, no inline. La fila solo lleva un puntero, por eso puedes tener varias columnas `TEXT` sin chocar contra el límite de 65 535 bytes por fila. - **`DEFAULT`**: los tipos `TEXT` no aceptan un valor por defecto literal antes de MySQL 8.0.13. ## Cuándo usar cada uno - Cadena corta con tope conocido (nombre, email, slug) → **`VARCHAR(n)`**. - Texto humano normal hasta 64 KiB (comentarios, descripciones) → **`TEXT`**. - Contenido que puede crecer hasta 16 MiB (artículos, Markdown largo, JSON mediano) → **`MEDIUMTEXT`**. - Datos enormes hasta 4 GiB (documentos, base64, logs) → **`LONGTEXT`** (y considera un object storage externo). Con eso ya tienes con qué diseñar tus tablas sin tener que rehacerlas después. Y si trabajas seguido con MySQL, te puede servir también [la diferencia entre WHERE y HAVING](/post/cual-es-la-diferencia-entre-where-y-having-en-mysql), otra duda clásica a la hora de escribir queries. ## Preguntas frecuentes ### ¿Cuál es el tamaño máximo de LONGTEXT en MySQL? 4 GiB: exactamente 4 294 967 295 bytes (2³²−1) por valor. ### ¿Cuántos caracteres caben en un TEXT? Depende de la codificación. En bytes el tope es 65 535; con `utf8mb4` (hasta 4 bytes por carácter) eso equivale a unos 16 383 caracteres, y con `latin1`, a 65 535. ### ¿Uso LONGTEXT o TEXT? El más chico que cubra tu caso real. `TEXT` (64 KiB) alcanza para casi todo; pasa a `MEDIUMTEXT` o `LONGTEXT` solo si el contenido lo justifica. Sobredimensionar complica índices y memoria. ### ¿TEXT o VARCHAR? `VARCHAR` para cadenas cortas con longitud acotada (se indexa completo y admite `DEFAULT`); `TEXT` para contenido grande o sin tope claro. ## Lecturas recomendadas - [Documentación oficial de MySQL: tipos de cadena](https://dev.mysql.com/doc/refman/8.0/en/string-type-overview.html) - [TINYTEXT, TEXT, MEDIUMTEXT, and LONGTEXT maximum storage sizes (Stack Overflow)](https://stackoverflow.com/questions/13932750/tinytext-text-mediumtext-and-longtext-maximum-storage-sizes) --- ### Adminer: gestor de bases de datos minimalista - URL: https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista - Markdown: https://www.angelcruz.dev/post/adminer-gestor-de-bases-de-datos-minimalista.md - Categoría: Bases de Datos - Fecha: 2019-09-21 - Excerpt: En mi trabajo actual me han preguntado la razón de usar adminer sobre phpMyAdmin. Este post trata de explicar esas razones --- title: "Adminer: gestor de bases de datos minimalista" excerpt: "En mi trabajo actual me han preguntado la razón de usar adminer sobre phpMyAdmin. Este post trata de explicar esas razones" date: "2019-09-21T19:57:49.000Z" category: "Bases de Datos" seo_title: "Adminer: gestiona bases de datos con un solo archivo PHP" seo_description: "Adminer es una herramienta de gestión de bases de datos en un único archivo PHP. Soporta MySQL, PostgreSQL, SQLite, MongoDB y más, con soporte para plugins y temas CSS." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Adminer inicio con el nombre de `phpMinAdmin` es una herramienta de gestión de bases de datos con todas las funciones escrita en PHP. Su principal característica es que consiste en un único archivo listo para implementar en nuestro servidor local o de producción. ### Con adminer puedes gestionar bases de datos: - MySQL - PostgreSQL - SQLite - MS SQL - Oracle - Firebird - SimpleDB - Elasticsearch - MongoDB Adminer es un archivo php que permite gestionar diferentes tipos de base de datos Adminer cuenta también con una serie de [plugins](https://www.adminer.org/en/plugins/) que permiten aumentar la versatilidad de esta herramienta. ### Tip Adminer cuenta con una serie de "skins" para cambiar la apariencia ya que la vista por defecto no es bonita. La apariencia de la interfaz de Adminer podemos modificarla de una manera muy sencilla empleando un archivo CSS llamado adminer.css. Este archivo debe estar en la misma carpeta que el archivo php de Adminer. Visitar: [adminer.org](https://www.adminer.org/en/) --- ### InterPlanetary File System aka IPFS - URL: https://www.angelcruz.dev/post/interplanetary-file-system-aka-ipfs - Markdown: https://www.angelcruz.dev/post/interplanetary-file-system-aka-ipfs.md - Categoría: Web - Fecha: 2019-09-21 - Excerpt: InterPlanetary File System (IPFS) es un nuevo protocolo de Internet que busca renovar todos los protocolos actuales ofreciendo una solución todo-en-uno con la que poder cubrir prácticamente cualquier necesidad. --- title: "InterPlanetary File System aka IPFS" excerpt: "InterPlanetary File System (IPFS) es un nuevo protocolo de Internet que busca renovar todos los protocolos actuales ofreciendo una solución todo-en-uno con la que poder cubrir prácticamente cualquier necesidad." date: "2019-09-21T19:52:33.000Z" category: "Web" seo_title: "IPFS: el sistema de archivos distribuido que reemplaza HTTP" seo_description: "IPFS es un protocolo descentralizado que identifica archivos por hash criptográfico en lugar de URL. Cada nodo almacena solo el contenido que le interesa, sin servidor central." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- El protocolo IPFS permite crear aplicaciones totalmente distribuidas, garantizando así su disponibilidad a lo largo de la red sin depender de un único servidor centralizado para brindar un servicio concreto. Este protocolo, de forma generalizada, es igual que la web actual, aunque implementando en él el protocolo bitTorrent para garantizar el acceso a toda la información que posee en todo momento. ### Características - Cada archivo y todos los bloques dentro de él reciben una huella digital única llamada hash criptográfico. - IPFS elimina las duplicaciones en la red y rastrea el historial de versiones de cada archivo. - Cada nodo de red almacena sólo el contenido que le interesa y cierta información de indexación que ayuda a determinar quién está almacenando qué. - Al buscar archivos, le está pidiendo a la red que busque nodos que almacenan el contenido detrás de un hash único. - Cada archivo se puede encontrar con nombres legibles por humanos usando un sistema de nombres descentralizado llamado IPNS. IPFS está disponible para: - Linux - Windows - Mac Puedes saber más leyendo: - [su documentación](https://ipfs.tech/docs/install/) - ir a la página de [ipfs.tech](https://ipfs.tech/) --- ### Cómo corregir a un jefe; claves para lidiar con proyectos sin plazos determinados - URL: https://www.angelcruz.dev/post/como-corregir-a-un-jefe-claves-para-lidiar-con-proyectos-sin-plazos-determinados-y-otras-noticias - Markdown: https://www.angelcruz.dev/post/como-corregir-a-un-jefe-claves-para-lidiar-con-proyectos-sin-plazos-determinados-y-otras-noticias.md - Categoría: Opinión - Fecha: 2019-09-21 - Excerpt: A la hora de corregir a un jefe, es importante evaluar si el error fue de poca importancia o algo que puede traerle perjuicios a la compañía. --- title: "Cómo corregir a un jefe; claves para lidiar con proyectos sin plazos determinados" excerpt: "A la hora de corregir a un jefe, es importante evaluar si el error fue de poca importancia o algo que puede traerle perjuicios a la compañía." date: "2019-09-21T19:43:09.000Z" category: "Opinión" seo_title: "Corregir a un jefe: claves para hacerlo sin fricciones" seo_description: "Cómo corregir errores de tu jefe sin crear conflictos y cómo manejar proyectos sin fecha de entrega. Estrategias prácticas para mejorar la comunicación en el trabajo." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ---

¿Cómo corregir a un jefe? Evitar situaciones incómodas y no dar la impresión de que uno está rebelándose están entre las primeras recomendaciones realizadas por la revista Quartz. La publicación también menciona que los empleados “tienen que elegir sus batallas”. En todo caso, lo mejor es evaluar si el error fue algo pequeño, de poca importancia o algo que puede traerle perjuicios a la compañía. La realidad es que hay algunos errores que no vale la pena corregir, afirma el artículo. A la vez, es esencial saber cuándo es el momento correcto para tener una conversación acerca del asunto. La peor estrategia, sin dudas, es abordar al jefe de forma repentina cuando está ocupado con otros temas.

Claves para lidiar con proyectos sin plazos determinados. Aunque muchas veces le tenemos miedo a las fechas de entrega, cuando directamente no existen podemos ponernos muy ansiosos ante la falta de planeamiento. ¿Cómo podemos estar al día con un gran proyecto pese a que no tenga un plazo definido de entrega? Establecer fechas límite, aunque sea para uno mismo, es una salida para este problema, aconseja la coach especializada en administración del tiempo Elizabeth Grace Saunders. Si esta estrategia no funciona, otra opción es pedirle a un amigo, colega de trabajo o hasta al mismo jefe que nos pregunte, periódicamente, en qué etapa del proyecto nos encontramos. Una última solución es darse recompensas (o imponerse penalidades) que estén asociadas a los resultados del trabajo.

Para leer más clic https://www.linkedin.com/pulse/c%25C3%25B3mo-corregir-un-jefe-claves-para-lidiar-con-sin-y-tendencias/

--- ### Mi primer componente para Laravel Nova - URL: https://www.angelcruz.dev/post/mi-primer-componente-para-laravel-nova - Markdown: https://www.angelcruz.dev/post/mi-primer-componente-para-laravel-nova.md - Categoría: Laravel - Fecha: 2019-08-24 - Excerpt: Básicamente lo que hice fue convertir el plugin Hello Dolly de WordPress a un componente de Nova llamado "card"... --- title: "Mi primer componente para Laravel Nova" excerpt: "Básicamente lo que hice fue convertir el plugin Hello Dolly de WordPress a un componente de Nova llamado \"card\"..." date: "2019-08-24T16:28:53.000Z" category: "Laravel" seo_title: "Mi primer componente para Laravel Nova: nova-hello-dolly" seo_description: "Versión del clásico plugin Hello Dolly de WordPress convertida en un card de Laravel Nova. Instalación con Composer y registro en el NovaServiceProvider en dos pasos." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- [Hello Dolly](https://wordpress.org/plugins/hello-dolly/) es un plugin clásico de WordPress por lo que se me ocurrió que lo podía convertir a un componente de Laravel Nova ya que actualmente estoy trabajando en un proyecto con esta genial herramienta. Para usarlo lo único que tienen que hacer es usar composer de la siguiente forma: ```shell composer require abr4xas/nova-hello-dolly ``` Luego hay que registrarlo en el archivo NovaServiceProvider de la siguiente forma: ```php 'POST', 'callback' => 'my_awesome_func', )); }); function my_awesome_func(WP_REST_Request $request) { // TODO } ``` Y para enviar los datos usamos la vieja y confiable funcion AJAX y que es algo como esto: ```js var dataValue = { "hello": "there" }; $.ajax({ url: 'http://wordpress.local/wp-json/myplugin/v1/foo', dataType: 'json', type: 'post', contentType: 'application/json', data: JSON.stringify( dataValue ), processData: false, success: function( res ){ // TODO }, }); ``` ## Ahora, la parte divertida (?) Como se obtiene la data que estamos enviando al backend para trabajar con ella? De seguro pueden encontrar por ahí en internet alguien que les diga: To get JSON data (or any raw input), use php://input. lo cual no esta del todo mal pero ya que estamos usando WordPress podemos aprovechar el objeto request. La documentación dice que: "By default, routes receive all arguments passed in from the request. These are merged into a single set of parameters, then added to the Request object, which is passed in as the first parameter to your endpoint." Entonces, podemos usar: ```php $param = $request['some_param']; // Or via the helper method: $param = $request->get_param( 'some_param' ); // You can get the combined, merged set of parameters: $parameters = $request->get_params(); // The individual sets of parameters are also available, if needed: $parameters = $request->get_url_params(); $parameters = $request->get_query_params(); $parameters = $request->get_body_params(); $parameters = $request->get_json_params(); $parameters = $request->get_default_params(); // Uploads aren't merged in, but can be accessed separately: $parameters = $request->get_file_params(); ``` Y para nuestro ejemplo estaríamos usando `$parameters = $request->get_json_params();` y accedemos a los parámetros de esta forma: `$parameters['hello']`. Espero que esto le sirva a alguien más. --- ### Mis paquetes favoritos de Laravel - URL: https://www.angelcruz.dev/post/mis-paquetes-favoritos-de-laravel - Markdown: https://www.angelcruz.dev/post/mis-paquetes-favoritos-de-laravel.md - Categoría: Laravel - Fecha: 2019-07-31 - Excerpt: Este es un pequeño listado de los paquetes que uso muy a menudo en proyectos, estaré actualizando el post poco a poco a medida de que encuentre más paquetes interesantes. --- title: "Mis paquetes favoritos de Laravel" excerpt: "Este es un pequeño listado de los paquetes que uso muy a menudo en proyectos, estaré actualizando el post poco a poco a medida de que encuentre más paquetes interesantes." date: "2019-07-31T16:05:16.000Z" category: "Laravel" seo_title: "Paquetes favoritos de Laravel: selección de Spatie y más" seo_description: "Lista curada de paquetes Laravel de uso frecuente: Media Library, Sitemap, Sluggable, Feed, Honeypot y Permission de Spatie, más Laravel Localization. Actualizada con nuevas incorporaciones." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/laravel-opengraph-image.png" --- - [Laravel Media Library](https://github.com/spatie/laravel-medialibrary) - [Laravel Site Map](https://github.com/spatie/laravel-sitemap) - [Laravel cookie consent](https://github.com/spatie/laravel-cookie-consent) - [Laravel sluggable](https://github.com/spatie/laravel-sluggable) - [Laravel feed](https://github.com/spatie/laravel-feed) - [Laravel honeypot](https://github.com/spatie/laravel-honeypot) - [Laravel localization](https://github.com/mcamara/laravel-localization) - [Laravel Permission](https://github.com/spatie/laravel-permission) --- ### El operador ternario php - URL: https://www.angelcruz.dev/post/el-operador-ternario-php - Markdown: https://www.angelcruz.dev/post/el-operador-ternario-php.md - Categoría: PHP - Fecha: 2019-07-31 - Excerpt: El uso de if-else y switch case es una parte esencial de la programación para evaluar condiciones hoy hablaré un poco sobre el operador ternario de php. --- title: "El operador ternario php" excerpt: "El uso de if-else y switch case es una parte esencial de la programación para evaluar condiciones hoy hablaré un poco sobre el operador ternario de php." date: "2019-07-31T03:16:28.000Z" lastModified: "2026-03-13T00:00:00.000Z" category: "PHP" author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" ogImage: url: "/images/open-graph/php-opengraph-image.png" seo_title: "Operador Ternario en PHP: Sintaxis, Elvis (?:) y Null (??)" seo_description: "Qué es y cómo se usa el operador ternario en PHP: sintaxis básica, operador Elvis (?:) y null coalescing (??). La guía de concepto; los casos de uso van en el artículo de ejemplos." tech_article: article_section: "PHP Fundamentals" keywords: "operador ternario php, sintaxis php, condicionales php" --- Este artículo forma parte de la [guía de operadores en PHP](/post/operadores-php), donde repaso todas las categorías (aritméticos, comparación, lógicos y más). Aquí me enfoco en el ternario. ## ¿Qué es el operador ternario en PHP? El **operador ternario** en PHP es un operador condicional que acorta una sentencia `if-else` en una sola línea. Evalúa una condición y devuelve uno de dos valores: el primero si la condición es verdadera, el segundo si es falsa. Su orden de ejecución es de izquierda a derecha. Ejemplo: ```php $edad = 20; echo ($edad >= 20) ? 'Pasa' : 'Fuera'; ``` - condition: La expresión a ser evaluada que retorna un valor booleano - statement 1: Es la sentencia que se ejecuta cuando el valor es true. - statement 2: Es la sentencia que se ejecuta cuando el valor es falso. ## La versión corta del operador ternario La sintaxis del operador ternario corto se puede utilizar omitiendo la parte central del operador ternario para una evaluación rápida y abreviada. También se conoce como el "Operador Elvis" Ejemplo: ```php $val = $_GET['user'] ?: 'default'; ``` El operador Elvis se puede utilizar para reducir la redundancia de sus condiciones y acortar la duración de sus tareas. Es el operador ternario con el segundo operador omitido. Devolverá el primer operador si es verdadero; de lo contrario, evalúa y devuelve su segundo operador. Si usamos el operador Elvis de esta forma, causará un error si `$_GET['user']` no está configurado, en lugar de escribir un código largo como este: `$val = isset($_GET['user']) ? $_GET['user'] : 'default';` ## Operador nula Reemplaza la operación ternaria junto con la función `isset()` que se usa para verificar si una variable dada es `NULL` o no y devuelve su primer operando si existe y no es `NULL`, de lo contrario, devuelve el segundo operando Ejemplo: ```php $user= $_GET['user'] ?? 'nobody'; ``` Ahora que entiendes cómo funciona el operador ternario, [descubre 10 ejemplos prácticos del operador ternario en PHP](/post/10-ejemplos-operador-ternario-php) para ver casos de uso reales en proyectos: validaciones, clases CSS dinámicas, API responses y más. --- ### Uruguay, ¿el nuevo "Silicon Valley"​? - URL: https://www.angelcruz.dev/post/uruguay-el-nuevo-silicon-valley - Markdown: https://www.angelcruz.dev/post/uruguay-el-nuevo-silicon-valley.md - Categoría: Opinión - Fecha: 2019-07-27 - Excerpt: "Sé que algunos de ustedes estarán pensando ‘¿Dónde está Uruguay?'. Metido entre Brasil y Argentina, con una población de 3.5 millones en un territorio más chico que el estado de Missouri, Uruguay es más conocido por sus playas hermosas, su ganado y su pasión por el fútbol. --- title: "Uruguay, ¿el nuevo \"Silicon Valley\"​?" excerpt: "\"Sé que algunos de ustedes estarán pensando ‘¿Dónde está Uruguay?'. Metido entre Brasil y Argentina, con una población de 3.5 millones en un territorio más chico que el estado de Missouri, Uruguay es más conocido por sus playas hermosas, su ganado y su pasión por el fútbol." date: "2019-07-27T01:49:24.000Z" category: "Opinión" seo_title: "Uruguay: el Silicon Valley de Sudamérica en cifras reales" seo_description: "Uruguay generó 1.200 millones de dólares en IT en 2016 con 700 empresas exportando software a 52 países. Harvard lo reconoce como el centro de software más avanzado de la región." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Un artículo de Christian Serron, director de Bros, publicado en la plataforma Medium, analiza el desarrollo de Uruguay en la industria del software bajo el título: "Uruguay: el Silicon Valley de Sudamérica", en el que hace comparaciones entre lo que llevó a California a liderar la innovación tecnológica mundial con el proceso vivido por Uruguay. > "Sé que algunos de ustedes estarán pensando ‘¿Dónde está Uruguay?'. Metido entre Brasily Argentina, con una población de 3.5 millones en un territorio más chico que el estado de Missouri, Uruguay es más conocido por sus playas hermosas, su ganado y su pasión por el fútbol. Sin embargo, con 1.200 millones de dólares generados en tecnología de la información en 2016 y en constante expansión, y con 700 compañías tecnológicas exportando software a 52 mercados diferentes, Uruguay también es el principal exportador de software per cápita en Sudamérica y el tercero en el mundo", comienza la nota. "Parece cierto que Uruguay tiene una gran cultura de desarrollo pero llamarlo el Silicon Valley de Sudamérica puede ser un poco presuntuoso, ¿no creen? Spoiler alert: no lo es", asegura. Prosigue destacando que Harvard identificó a Uruguay como "uno de los centros de desarrollo de software más avanzados de la región" y recuerda una frase de Tina Seelig, directora del Stanford Technology Ventures Program de la Universidad de Stanford, que dijo: > "Mi primer viaje a Uruguay me abrió los ojos. No tenía idea de que este pequeño país tenía tanto para ofrecer. Desde el espíritu emprendedor a las magníficas costas, está claro que Uruguay está preparado para subir al escenario mundial". Según el [artículo de Bros](https://medium.com/bros/uruguay-the-silicon-valley-of-south-america-8cdef0bbcadc), de la misma forma que el éxito de Silicon Valley no es producto de la casualidad, el suceso de Uruguay en las tecnologías de la información es "el resultado de un plan maestro sofisticado del gobierno, combinado con una serie de circunstancias oportunas". Lee el artículo completo siguiendo este [enlace](https://medium.com/bros/uruguay-the-silicon-valley-of-south-america-8cdef0bbcadc). --- ### SysAdmin Appreciation Day - URL: https://www.angelcruz.dev/post/sysadmin-appreciation-day - Markdown: https://www.angelcruz.dev/post/sysadmin-appreciation-day.md - Categoría: Opinión - Fecha: 2019-07-26 - Excerpt: Para los aun no interiorizados en el tema, hoy es el SysAdmin Appreciation Day. ¿Y qué se celebra? --- title: "SysAdmin Appreciation Day" excerpt: "Para los aun no interiorizados en el tema, hoy es el SysAdmin Appreciation Day. ¿Y qué se celebra?" date: "2019-07-26T17:01:55.000Z" category: "Opinión" seo_title: "SysAdmin Day: historia y por qué se celebra cada julio" seo_description: "El SysAdmin Appreciation Day fue creado en el año 2000 por Ted Kekatos y se celebra el último viernes de julio. Un día para reconocer el trabajo silencioso de los administradores de sistemas." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Tiene sus comienzos en el año 2000 cuando Ted Kekatos, un profesional de IT, propuso que un día al año se reconozca a los Administradores de Sistemas. Kekatos se inspiró en una publicidad de Hewlett-Packard, en la que podía verse a un SysAdmin agasajado con flores y canastas de frutas, tras haber instalado exitosamente una impresora. Este día, entonces es para mostrar el agradecimiento por la labor de los Administradores de Sistemas. El primer SysAdmin Day se celebró el 28 de julio de 2000. El SysAdmin Day tiene lugar el último viernes del mes de Julio de cada año, con cita en el ciberespacio. Y tu, ¿Ya saludaste a tu amigo SysAdmin? Mas información: - [Sysadmin Day](https://sysadminday.com/) - [Día del SysAdmin: mitos y verdades sobre este ser mitológico](https://www.welivesecurity.com/la-es/2018/07/27/dia-sysadmin-mitos-verdades-este-profesional/) --- ### Operadores en JavaScript, segunda parte (final) - URL: https://www.angelcruz.dev/post/operadores-en-javascript-segunda-parte-final - Markdown: https://www.angelcruz.dev/post/operadores-en-javascript-segunda-parte-final.md - Categoría: JavaScript - Fecha: 2019-07-15 - Excerpt: Domina operadores avanzados en JavaScript (Parte 2): lógicos, comparación y asignación. Ejemplos prácticos para escribir código más eficiente. --- title: "Operadores en JavaScript, segunda parte (final)" excerpt: "Domina operadores avanzados en JavaScript (Parte 2): lógicos, comparación y asignación. Ejemplos prácticos para escribir código más eficiente." date: "2019-07-15T00:10:27.000Z" category: "JavaScript" seo_title: "Operadores JavaScript: AND, OR, matemáticos y módulo" seo_description: "Guía de operadores JavaScript (parte 2): AND, OR, operadores matemáticos básicos (+, -, *, /) y el operador módulo %. Incluye notación abreviada con asignación compuesta y ejemplos paso a paso." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ### Operador lógico AND La operación lógica AND obtiene su resultado combinando dos valores booleanos. Este operador se representa con el símbolo `&&` y su resultado solamente es true si los dos operandos son true. ```js var valor1 = true; var valor2 = false; resp = valor1 && valor2; // resp = false valor1 = true; valor2 = true; resp = valor1 && valor2; // resp = true ``` ### Operador OR Al igual que el operador lógico AND este operador combina dos valores booleanos. Este operador se representa con el símbolo `||` y su resultado es true si alguno de los dos operandos es true. ```js var valor1 = true; var valor2 = false; resp = valor1 || valor2; // resp = true valor1 = false; valor2 = false; resp = valor1 || valor2; // resp = false ``` ### Matemáticos JavaScript nos permite hacer manipulaciones matemáticas sobre el valor de las variables numéricas. Los operadores definidos son: suma (+), resta (-), multiplicación (*) y división (/). ```js var numero1 = 10; var numero2 = 5; resp = numero1 / numero2; // resp = 2 resp = 3 + numero1; // resp = 13 resp = numero2 – 4; // resp = 1 resp = numero1 * numero 2; // resp = 50 ``` A parte de los cuatro operadores básicos predefenidos existe otro que es el operador "módulo", que calcula el resto de la división entera de dos números. Si se divide por ejemplo 10 y 5, la división es exacta y da un resultado de 2. El resto de esa división es 0, por lo que módulo de 10 y 5 es igual a 0. Sin embargo, si se divide 9 y 5, la división no es exacta, el resultado es 1 y el resto 4, por lo que módulo de 9 y 5 es igual a 4. El operador módulo en JavaScript se indica mediante el símbolo %, que no debe confundirse con el cálculo del porcentaje: ```js var numero1 = 10; var numero2 = 5; resultado = numero1 % numero2; // resultado = 0 numero1 = 9; numero2 = 5; resultado = numero1 % numero2; // resultado = 4 ```
```js var numero1 = 5; numero1 += 3; // numero1 = numero1 + 3 = 8 numero1 -= 1; // numero1 = numero1 - 1 = 4 numero1 *= 2; // numero1 = numero1 * 2 = 10 numero1 /= 5; // numero1 = numero1 / 5 = 1 numero1 %= 4; // numero1 = numero1 % 4 = 1 ``` --- ### Operadores en JavaScript, primera parte - URL: https://www.angelcruz.dev/post/operadores-en-javascript-primera-parte - Markdown: https://www.angelcruz.dev/post/operadores-en-javascript-primera-parte.md - Categoría: JavaScript - Fecha: 2019-03-25 - Excerpt: Aprende operadores en JavaScript desde cero (Parte 1): aritméticos, asignación y concatenación. Fundamentos esenciales con ejemplos prácticos. --- title: "Operadores en JavaScript, primera parte" excerpt: "Aprende operadores en JavaScript desde cero (Parte 1): aritméticos, asignación y concatenación. Fundamentos esenciales con ejemplos prácticos." date: "2019-03-25T03:52:14.000Z" category: "JavaScript" seo_title: "Operadores en JavaScript: asignación, incremento y lógicos" seo_description: "Guía de operadores JavaScript (parte 1): asignación, incremento prefijo y sufijo, y operadores lógicos de negación. Diferencias entre ++i y i++ con ejemplos prácticos para principiantes." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ### Operadores de Asignación El operador de asignación es el más utilizado y el más sencillo. Este operador se utiliza para guardar un valor específico en una variable como lo vimos en el post anterior cuando declarabamos una variable con var, let o const. ### Operadores de incremento y decremento Estos dos operadores solamente son válidos para las variables numéricas y se utilizan para incrementar o decrementar en una unidad el valor de una variable. #### Incremento ```js var numero = 6; ++numero; console.log(numero); // numero = 7 ``` #### Decremento ```js var numero = 6; --numero; console.log(numero); // numero = 5 ``` #### Diferencias ```js var numero1 = 5; var numero2 = 2; numero3 = numero1++ + numero2; var numero1 = 5; var numero2 = 2; numero3 = ++numero1 + numero2; ```
Tomando en cuenta la nota anterior los resultados serían: ```js // numero3 = 7, numero1 = 6 // numero3 = 8, numero1 = 6 ``` #### Lógicos Estos operadores son importantes ya que con ellos podemos hacer decisiones sobre las instrucciones que debe seguir nuestra aplicación basados en ciertas condiciones. #### Negación Se utiliza para obtener el valor contrario al valor de la variable: Ejemplo ```js var visible = true; console.log(!visible); // false ``` La negación lógica se obtiene usando el símbolo `!` como prefijo al identificador de la variable. Algo importante a tener en cuenta: - Si la variable contiene un número, se transforma en false si vale 0 y en true para cualquier otro número (positivo o negativo, decimal o entero). - Si la variable contiene una cadena de texto, se transforma en false si la cadena es vacía ("") y en true en cualquier otro caso. Ejemplo ```js var cantidad = 0; vacio = !cantidad; // vacio = true cantidad = 2; vacio = !cantidad; // vacio = false var mensaje = ""; mensajeVacio = !mensaje; // mensajeVacio = true mensaje = 'Bienvenido'; mensajeVacio = !mensaje; // mensajeVacio = false ``` Esto será todo por el momento, en otro post continuaré con los otros operadores que hacen falta: - AND - OR - Matemáticos - Relacionales --- ### Declaración de variables usando var, let, const y tipos de datos - URL: https://www.angelcruz.dev/post/declaracion-de-variables-usando-var-let-const-y-tipos-de-datos - Markdown: https://www.angelcruz.dev/post/declaracion-de-variables-usando-var-let-const-y-tipos-de-datos.md - Categoría: JavaScript - Fecha: 2019-03-09 - Excerpt: Aquí escribo un poco sobre las variables usando var, let, const y los tipos de datos que pueden ser asignados a esas variables. --- title: "Declaración de variables usando var, let, const y tipos de datos" excerpt: "Aquí escribo un poco sobre las variables usando var, let, const y los tipos de datos que pueden ser asignados a esas variables." date: "2019-03-09T16:34:27.000Z" category: "JavaScript" seo_title: "Variables en JavaScript: var, let, const y tipos de datos" seo_description: "Guía de declaración de variables en JavaScript con var, let y const. Diferencias de scope (global, function y block scope) con ejemplos prácticos para principiantes." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- Los tipos de datos son básicamente variables o constantes. - Las variables pueden ser cambiadas - Las constantes siguen siendo las mismas. ### Hablemos sobre var Las variables en JavaScript son contenedores que contienen datos reutilizables. Es comun inicializar variables de esta forma: ```js var algo; ``` Los valores o tipos de datos que podemos asignar a una variable son: - String - Number - Boolean - Null - Undefined - Array / Object ### Ejemplo: ```js var nombre = 'Angel'; // String var edad = 40; // Number var hijos = true | false; // Boolean var telefono = null; // Null var negro; // Undefined var arreglo = ['a','b','c',]; // Array var objeto = { nombre: 'Angel', edad: 40, }; ``` ### Como declarar una variable: - Los nombres deben comenzar con una cadena en minúscula. - Los nombres no pueden contener símbolos o comenzar con símbolos. - Los nombres no pueden comenzar con un número. - Los nombres pueden contener una combinación de cadenas en mayúsculas, cadenas en minúsculas y números.
```js var name = 'Angel'; console.log(name); // Angel var name = 'Chris'; console.log(name); // Chris ``` ### El scope de var Es importante tener en cuenta el alcance (scope) de una variable declarada con var. Ejemplo, si tenemos declarada la siguiente variable: ```js var name = 'Angel'; ``` La misma tiene un alcance global y puede ser utilizada donde se necesite. ### ¿Que pasa cuando usamos una función? Aquí el scope de la variable declarada con var cambia ya que solo tiene acceso dentro de la función donde fue declarada, ejemplo: ```js function sayMyName() { var name = 'Angel'; console.log(name); } sayMyName(); ``` Efectivamente va a funcionar ya que veremos el output en la consola de desarrollo, a esto se le conoce como `function scope`
### Y ya por último, halaremos sobre `block scope` Por ejemplo: ```js var age = 100; if (age > 12) { var dogYears = age * 7; console.log('Tienes ' + dogYears + ' años de perro') // Tienes 700 años de perro } ``` A diferencia de `function scope` con los `block scope` ocurre algo interesante y es que se pueden acceder a las variables que esten dentro de `{}`. Si vamos a nuestra consola y escribimos `dogYears` vamos a tener como resultado el valor 700 ### Hablemos sobre let y const Las variables declaradas con `let` y `const` son del tipo `block scope` Por ejemplo: ```js var age = 100; if (age > 12) { let dogYears = age * 7; console.log('Tienes ' + dogYears + ' años de perro') // Tienes 700 años de perro } ``` Lo de arriba funciona exactamente igual, la única diferencia estaría en el momento de acceder a la variable `dogYears` vamos a obtener un error de variable no declarada ya que la variable declarada con `let` sólo se puede acceder y usar dentro de `{}` y esto también ocurre si cambiamos `let` por `const`. --- ### Hola, de nuevo! - URL: https://www.angelcruz.dev/post/hola-de-nuevo - Markdown: https://www.angelcruz.dev/post/hola-de-nuevo.md - Categoría: Opinión - Fecha: 2019-03-06 - Excerpt: Estoy estrenando nuevo blog y nuevo dominio. --- title: "Hola, de nuevo!" excerpt: "Estoy estrenando nuevo blog y nuevo dominio." date: "2019-03-06T17:47:27.000Z" category: "Opinión" seo_title: "Nuevo blog, nuevo dominio .dev — Laravel, Vue y React" seo_description: "Estreno de angelcruz.dev, un nuevo blog con compromiso de publicar semanalmente sobre Laravel, Vue.js y React. Reflexión sobre aprendizajes del último año como desarrollador." author: name: "angel cruz" picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/images/me/angel-cruz.png" --- ![](https://media1.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif?cid=ecf05e4771q244cnjbxrtw9uf27ecx8zaksiehsrvbdhgu4u&ep=v1_gifs_search&rid=giphy.gif&ct=g) Aprovechando que ahora se pueden comprar dominios .dev (busca el tuyo aquí get.dev) tomé la iniciativa de crear un nuevo blog pero con más seriedad que antes, ahora realmente haré el esfuerzo de escribir mínimo una vez por semana. ### ¿Que habrá de nuevo aquí? Este último año he aprendido muchas cosas nuevas como VueJS, he mejorado lo que sé sobre laravel y últimamente he estado aprendiendo a usar React por lo que voy a escribir mucho (eso espero xD) sobre esas tecnologías.