Todo el código de shinobis.com fue escrito con Claude como coding partner. Cada función, cada query, cada archivo PHP de los últimos seis meses. No copio y pego sin revisar. Pero sí confío en el output después de verificarlo. La mayoría del tiempo funciona.

Tres veces no funcionó. Y cada vez aprendí algo sobre PHP que seis meses de tutoriales no me habrían enseñado. No porque los tutoriales no cubran esos temas. Porque los bugs que rompen tu código en producción te enseñan a un nivel que leer sobre el tema nunca alcanza.

Bug 1: require_once ejecuta código

El auto-linker es un script que corre como cron diario a las 6AM. Lee todos los posts publicados, analiza las conexiones entre ellos, y actualiza los links internos automáticamente. Claude lo escribió como un script ejecutable directo: abre la conexión a la base de datos, consulta los posts, y procesa todo en el scope global del archivo.

El problema apareció cuando necesité usar una función del auto-linker desde admin.php. Hice require_once del archivo. PHP cargó el archivo, y al cargarlo ejecutó toda la lógica del scope global. Cada vez que abría el panel de administración, el auto-linker procesaba todos los posts publicados. La página tardaba 8 segundos en cargar.

El código antes del fix:

// auto-linker.php
require_once __DIR__ . '/functions.php';

// Esta lógica se ejecutaba SIEMPRE que admin.php hacía require_once
$pdo = getDB();
$posts = $pdo->query("SELECT * FROM posts WHERE status = 'published'");
// ... procesaba todos los posts cada vez que admin.php cargaba

El fix:

// auto-linker.php
require_once __DIR__ . '/functions.php';

// Solo ejecutar si se llama directamente, no cuando se incluye
if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
    $pdo = getDB();
    $posts = $pdo->query("SELECT * FROM posts WHERE status = 'published'");
    // ... solo corre cuando el cron lo llama directamente
}

// Las funciones están disponibles para require_once sin ejecutar lógica
function addAutoLinks($postId, $langCode) {
    // ...
}

Lo que aprendí: require_once en PHP no solo hace las funciones disponibles. Carga Y ejecuta todo el código que está en el scope global del archivo. Claude generó el script como ejecutable directo porque eso es lo que le pedí. No anticipó que otro archivo lo incluiría después. Un developer con experiencia en PHP sabe que cualquier archivo que pueda ser incluido necesita proteger su lógica de ejecución. Claude no hizo esa distinción porque no tenía contexto del sistema completo.

Bug 2: Dos funciones con el mismo nombre

El panel de administración creció durante semanas. En una sesión le pedí a Claude una función para actualizar settings del sitio. La llamó handleUpdateLinks. El nombre no era ideal pero funcionaba. Doscientas líneas después, en otra sesión, necesité una función para actualizar los links de un post. Claude la llamó handleUpdateLinks.

PHP no permite dos funciones con el mismo nombre. Fatal error: Cannot redeclare function.

El código antes del fix:

// admin.php

function handleUpdateLinks() {
    // Esta función actualizaba SETTINGS
    $siteName = $_POST['site_name'];
    updateSetting('site_name', $siteName);
}

// ... 200 líneas después ...

function handleUpdateLinks() {
    // Esta función actualizaba LINKS de posts
    $postId = $_POST['post_id'];
    // PHP fatal error: Cannot redeclare function
}

El fix fue renombrar la primera función a handleUpdateSettings. Trivial. Lo interesante es por qué pasó.

Claude no tiene memoria entre sesiones. Cuando generó la segunda función, no sabía que la primera existía. Nombró ambas con la convención lógica para lo que hacían en ese momento. El crash fue inevitable. Un developer que trabaja en el mismo archivo durante semanas habría notado el conflicto. Claude, que ve cada sesión como un universo nuevo, no puede.

Lo que aprendí: la IA es excelente para generar funciones individuales. Es mala para mantener consistencia en un archivo que crece a lo largo de múltiples sesiones. La responsabilidad de la coherencia del sistema es del humano, no de la herramienta.

Bug 3: echo antes de header()

El markdown-negotiator.php tiene dos modos: servir la página principal como Markdown, o servir un post individual como Markdown. Claude escribió ambos modos en el mismo archivo pero con patrones diferentes.

Para el homepage usó echo directo:

if ($slug === null) {
    $siteName = getLocalizedSetting('site_name', $langCode);
    
    echo "# " . $siteName . "\n\n";
    echo "> " . $siteDesc . "\n\n";
    echo "## Recent Posts\n\n";
    
    // ... más echos ...
    
    header('x-markdown-tokens: ' . $tokenEstimate);
    // CRASH: Cannot modify header information - headers already sent
}

Para los posts individuales usó concatenación en una variable $output y envió el header antes del echo. El patrón correcto. Pero no lo aplicó en el homepage.

El fix:

if ($slug === null) {
    $siteName = getLocalizedSetting('site_name', $langCode);
    
    $output = "# " . $siteName . "\n\n";
    $output .= "> " . $siteDesc . "\n\n";
    $output .= "## Recent Posts\n\n";
    
    // ... más concatenación ...
    
    $tokenEstimate = (int)(str_word_count($output) * 1.3);
    header('x-markdown-tokens: ' . $tokenEstimate);
    
    echo $output;
}

PHP requiere que todos los header() se envíen antes de cualquier output al navegador. Claude conoce esta regla. La aplicó correctamente en una sección del archivo. No la aplicó en otra sección del mismo archivo. La inconsistencia no es un error de conocimiento. Es un error de atención. La IA no revisa su propio código contra sí mismo. Fue Gemini, en una auditoría del archivo, quien detectó este bug.

Lo que los tres bugs tienen en común

Ninguno es un error de sintaxis. Los tres son errores de contexto. Claude sabe que require_once ejecuta código. Sabe que PHP no permite funciones duplicadas. Sabe que header() va antes de echo. Pero no tiene la visión del sistema completo que un developer humano construye al trabajar en el mismo proyecto durante meses.

El primer bug es falta de contexto arquitectónico. Claude no sabía que otro archivo incluiría el script. El segundo es falta de memoria entre sesiones. Claude no recordaba la función anterior. El tercero es falta de consistencia interna. Claude aplicó el patrón correcto en un lugar y no en otro del mismo archivo.

Cada uno de estos bugs me enseñó algo específico sobre PHP que no habría aprendido leyendo documentación. La diferencia entre require_once que carga y ejecuta versus include que solo carga. El patrón if basename para proteger scripts de cron. La regla de headers antes de output y por qué existe a nivel de protocolo HTTP.

No aprendí estas cosas porque quise estudiar PHP. Las aprendí porque mi código se rompió en producción y tuve que entender por qué. La IA generó el código. El bug me obligó a entenderlo. El resultado es que soy mejor developer que antes de cada bug.

Usar IA como coding partner no reemplaza el aprendizaje. Lo redirige. En vez de aprender de tutoriales, aprendes de los errores de tu herramienta. Y un error en producción enseña más rápido que cualquier tutorial.