Contactar →
← Todas las entradas
Decisiones2026.06.03

Seis categorías, elegidas antes del primer post

Antes de que existiera un solo post, este log ya era una content collection, solo que vacía. Sin posts, sin borradores, sin nada migrado desde otro sitio, solo un schema esperando un contenido que todavía no existía. Es una posición inusual desde la que diseñar una taxonomía, porque no hay ningún conjunto de posts reales que ordenar ni patrones que observar en ellos. Normalmente dejarías que las categorías emergieran de lo que realmente has escrito, y luego formalizarías las que se hubieran consolidado. Aquí, las categorías tuvieron que venir primero, como una apuesta sobre de qué acabaría tratando este log, hecha con cero posts como evidencia.

La apuesta se decantó por seis categorías fijas: architecture, security, operations, postmortem, decisions, services. Fijas en el sentido de un enum cerrado, no de una lista orientativa: src/config/log.ts define LOG_CATEGORIES como una tupla const, y cualquier otro sitio que necesite conocer las categorías las deriva de ahí en lugar de declarar su propia lista.

Reutilizar límites de sección que ya existían

Tres de esos seis nombres (architecture, security, operations) no son vocabulario nuevo inventado para el blog. Son los mismos nombres que las páginas estructurales que ya existían en este sitio: /system, /security y /operations son rutas reales e independientes que documentan la infraestructura real: segmentación de red, modelo de ingress, almacenamiento, el catálogo de servicios, el pipeline de deploy, observability, backups. Esas páginas existen y se mantienen independientemente de que el log haya mencionado alguna vez algo de eso.

La elección de categorías se ajusta deliberadamente a ese límite ya existente en lugar de inventar uno paralelo. Un post archivado bajo architecture es un post sobre el mismo tipo de cosa que documenta /system (topología, compute, decisiones de almacenamiento), solo que narrado como una entrada de un log en lugar de descrito como el estado actual en una página de referencia. Llamar a la categoría del blog de cualquier otra forma (“infra”, “diseño”, “plataforma”) habría creado un segundo vocabulario que describe el mismo territorio que las páginas estáticas ya delimitan, y un lector (o un yo futuro, escribiendo el post número cuarenta) tendría que mantener ambos vocabularios en la cabeza y hacer la correspondencia entre ellos. Reutilizar el nombre hace que esa correspondencia sea gratis: si ya sabes qué cubre /security, ya sabes qué cubre la categoría security.

postmortem y services no se corresponden con una página estática de la misma forma directa, pero siguen el mismo instinto: nombrar la categoría según el tipo de cosa que es estructuralmente, no según un tema que resulte estar de moda este mes. Un postmortem es una forma narrativa concreta (pasó algo, esto es lo que cambió después), independiente de qué subsistema trate. services trata de qué se ejecuta y cómo se accede a ello, más cerca de una entrada de catálogo que de una narración.

Decisions absorbiendo lo que podrían haber sido páginas estáticas

decisions es la única categoría genuinamente nueva respecto a la estructura ya existente del sitio, y carga con un peso de diseño real. El comentario en el schema de Zod en src/content.config.ts lo dice sin rodeos: decisions, es decir, registros de decisiones de arquitectura (ADR), viven en el log en lugar de como páginas estáticas separadas bajo /architecture (o bajo /system, que es el nombre real que este sitio usa para esa sección).

Vale la pena detenerse en esto, porque el patrón más habitual es el contrario: una sección dedicada /adr o /decisions, construida a mano, separada de un blog. El razonamiento en contra de eso aquí es que un ADR ya es, estructuralmente, una entrada de log: tiene un momento en el tiempo, una decisión concreta, un conjunto concreto de alternativas consideradas, y no está pensado para editarse después como un documento vivo, al contrario que una página de referencia. /system describe lo que es cierto ahora; un post de decisions describe por qué una opción ganó a otra, en el momento en que se decidió. Darle su propia sección estática habría significado construir y mantener un segundo tipo de contenido que hace casi el mismo trabajo que ya hace la colección del log (el mismo pipeline de renderizado, los mismos metadatos por post, la misma estructura de una carpeta por slug), solo que con otra etiqueta. Archivarlo en el log como categoría significa que los ADR obtienen historial versionado, tags y una fecha de publicación gratis, y que hay exactamente una colección que mantener en lugar de dos.

Por qué un enum cerrado, no categorías en texto libre

La forma mecánica de la decisión importa tanto como los seis nombres en sí. LOG_CATEGORIES es una tupla fija, content.config.ts deriva su enum category de Zod directamente de ella, y categoryColor (también en log.ts) y categoryMeta (en src/i18n/dictionaries/log.ts, tanto para en como para es) están tipados contra esa misma lista. Añadir una séptima categoría implica editar tres sitios concretos, y TypeScript se negará a compilar hasta que los tres estén actualizados: categoryColor es un Record<LogCategory, string>, así que una categoría nueva sin entrada de color es un error de tipos, no una brecha silenciosa; categoryMeta en ambos diccionarios de locale está tipado satisfies CategoryMeta, así que una categoría a la que le falte su etiqueta o descripción en cualquiera de los dos idiomas falla de la misma manera.

Las categorías en texto libre se habrían saltado toda esa fricción, y ese es exactamente el problema que tienen. Un campo de string abierto permite que se cree una categoría por accidente: un error tipográfico, una etiqueta improvisada elegida porque encajaba bien para un post concreto, un casi-duplicado de algo que ya existía escrito de forma ligeramente distinta. Nada de eso aparece como un error; simplemente se va acumulando como categorías con un solo post cada una, o categorías que en silencio bifurcan lo que debería haber sido el mismo cajón. Un enum cerrado convierte añadir una categoría en un acto deliberado con una lista de comprobación impuesta por el compilador, lo cual es un pequeño impuesto que se paga rara vez, a cambio de no acumular nunca deuda de taxonomía por accidente.

La compensación es real: seis categorías decididas con cero posts como evidencia es una apuesta genuina, y una apuesta hecha tan pronto puede estar equivocada. Si más adelante aparece un séptimo tipo de post estructuralmente distinto (algo que no encaja en architecture, security, operations, postmortem, decisions ni services), el enum tendrá que crecer, y TypeScript se asegurará de que ese crecimiento toque todos los sitios que necesita tocar. Esa fricción es precisamente el objetivo, no un fallo del plan: sale más barato pagar un pequeño coste forzado y verificado por el sistema de tipos las pocas veces que hace falta añadir una categoría de verdad, que dejar la puerta abierta desde el primer día a que las categorías se descontrolen.

Color e idioma como funciones forzadoras, no como decoración

El mapa categoryColor en src/config/log.ts parece un detalle cosmético (cada categoría recibe una variable CSS: architecture y decisions comparten --amber, security y services comparten --cyan, postmortem tiene --terracotta en solitario), pero en realidad está haciendo el mismo trabajo de aplicación de reglas que el propio enum, solo que visualmente. Como está tipado como Record<LogCategory, string>, una categoría sin color asignado es un error de compilación, no un post que se renderiza en silencio sin acento de color. Eso significa que el diseño visual del log nunca puede desincronizarse en silencio de la taxonomía: no hay forma de añadir una categoría y olvidarse de darle un aspecto, porque el sistema de tipos no permite que la categoría exista a medio configurar.

El diccionario de idiomas lleva esa misma disciplina un paso más allá. categoryMeta vive en src/i18n/dictionaries/log.ts, una vez para en y otra para es, y ambos están tipados satisfies CategoryMeta, una forma derivada, de nuevo, de la misma unión LogCategory. Eso significa que una categoría no puede existir con etiqueta y descripción en inglés pero sin su equivalente en español, ni al revés. Para un sitio bilingüe, eso no es un detalle menor. Un campo de categoría en texto libre abierto habría hecho perfectamente posible acabar con categorías que solo llegaron a nombrarse en el idioma en el que se escribió el post que las introdujo, dejando que el filtro de categorías de la interfaz del otro idioma mostrara un espacio en blanco o un texto de respaldo para algo que debería tener una etiqueta traducida de verdad. El enum cerrado convierte “hemos traducido todas las categorías” de algo que un humano tiene que acordarse de comprobar en algo que hace fallar el build si está mal.

Lo que estuvo a punto de ir por otro camino

Vale la pena nombrar la alternativa que realmente estuvo sobre la mesa y se descartó, porque el razonamiento en su contra es tan revelador como el razonamiento a favor del diseño elegido. La alternativa obvia a un enum fijo es exactamente lo que la mayoría de las plataformas de blog traen por defecto: un campo de categoría en texto libre, con tags encima como un segundo eje, igualmente abierto, a otra granularidad; dos sabores del mismo mecanismo sin restricciones. Es un diseño perfectamente viable para un blog donde de verdad se espera que las categorías sean numerosas y cambiantes, y donde no existe ninguna estructura de sitio previa a la que puedan mapearse.

Ninguna de esas dos condiciones se daba aquí. Este sitio ya tenía exactamente los límites que una taxonomía querría: tres secciones estáticas que cubren architecture, security y operations, más el hueco con forma de ADR que se convirtió en decisions; así que un campo de categoría abierto habría estado resolviendo un problema que la estructura ya existente del sitio ya había resuelto, solo que mal, al re-derivar un conjunto de cajones que ya existía en otro sitio bajo otras reglas. Y como esas secciones ya existentes son un conjunto pequeño, estable y deliberadamente curado, en lugar de algo que crece semana a semana, reflejarlas como un enum cerrado no fue una restricción impuesta al blog desde fuera: fue reconocer que los límites naturales de categoría del blog ya estaban fijados en la práctica, lo dijera el schema o no.

Los tags como válvula de escape

Nada de esto funciona, sin embargo, si cada post tiene que encajarse a la fuerza en uno de seis cajones sin ninguna forma más fina de describir de qué trata realmente. Para eso están tags: un simple string[], con valor por defecto vacío, completamente sin restricciones. Un post puede tener category: "security" y llevar tags como ["vpn", "wireguard-style", "access-control"], y esos tags no cuestan nada inventarlos, reutilizarlos o abandonarlos. No hay ninguna comprobación del compilador sobre los tags ni ningún enum que extender, porque los tags no están pensados para ser estructurales: son la válvula de escape para la especificidad a nivel de tema que no merece su propia categoría.

Esa separación hace un trabajo real: significa que la decisión de las seis categorías no tiene que anticipar todos los temas que este log llegará a cubrir alguna vez, solo las seis formas estructurales que puede tomar un post. Todo lo demás, más específico que eso (qué protocolo, qué subsistema, qué clase de incidente), vive en los tags, donde equivocarse o cambiar de opinión más adelante no cuesta más que editar un array en el frontmatter.

El enum de categorías es solo la mitad de la historia del schema de esta colección; consulta Dos content collections, un blog para ver por qué el lado en inglés y el lado en español del log son dos colecciones completamente separadas, en lugar de un único schema con una forma de campos traducidos.

← Todas las entradas