name: content-factory description: | Genera contenido multicanal (hasta 35 canales, 4 idiomas) a partir de un source.md usando el sistema WriterBatch Zoopa. Orquesta seleccion de canales, preguntas pre-publicacion, generacion de outputs y revision ortografica/estilo. Triggers: "content-factory", "launch", "lanzar proyecto", "generar contenido", "writerbatch", "publicar contenido", "content batch", "/content-factory".
Skill: Content Factory — WriterBatch Zoopa
Proposito
Generar todos los outputs de contenido multicanal para un proyecto a partir de un unico source.md. El flujo completo incluye seleccion de canales, preguntas pre-publicacion, generacion con system prompt especializado y revision obligatoria de ortografia y estilo.
Archivos Clave
Referencias editoriales (markdown)
| Elemento | Ruta |
|---|---|
| Catalogo de canales | ~/.claude/skills/content-factory/references/channel-catalog.md |
| Reglas ortografia | ~/.claude/skills/content-factory/references/orthography-rules.md |
| Lecciones aprendidas | ~/.claude/skills/content-factory/references/lessons-learned.md |
| Componentes HTML para blogs | ~/.claude/skills/content-factory/references/blog-html-components.md |
| Estructura del source.md (3-block brief) ⭐ | ~/.claude/skills/content-factory/references/content-brief-structure.md |
| Patrones editoriales (killer line, paradoja, "cuándo X suma") ⭐ | ~/.claude/skills/content-factory/references/editorial-patterns.md |
| Voz LinkedIn · Carlos Ortet (cuenta personal) ⭐ | ~/.claude/skills/content-factory/references/linkedin-voice-carlos-ortet.md |
| Estilo Substack (email + permalink, no blog) ⭐ | ~/.claude/skills/content-factory/references/substack-style.md |
| Voz X/Twitter (humano, no LLM) ⭐ | ~/.claude/skills/content-factory/references/x-twitter-voice-style.md |
⭐ = nuevos en mayo 2026. linkedin-voice-carlos-ortet.md es OBLIGATORIO leer y aplicar antes de redactar linkedin.md o linkedin_ca.md cuando el autor sea Carlos Ortet (cuenta personal). Captura las correcciones reales que aplicó al post "La siguiente bestIA" (2026-05-10): hook coloquial-irónico, conectores hablados, glosa parentética para audiencia mixta, auto-ironía, balance "soy pro-X pero...", pregunta abierta final, política estricta de anonimización (clientes con NDA → eliminar caso, no anonimizar a nivel sectorial), y otros patrones documentados.
substack-style.md es OBLIGATORIO leer y aplicar antes de redactar substack_ready.txt. Captura los patrones reales validados con el proyecto AI Chips Economy (mayo 2026): subject line ≤55 chars sin marca, preheader que expande sin repetir, headers H2 editoriales/aforísticos (no descriptivos), conversión de componentes HTML del blog a prosa equivalente, tono conversacional con conectores hablados, cierre obligatorio con CTA suscripción + lista de versiones long-form (blog ES/EN/CA + Medium). Substack NO es un blog Wordpress condensado — es un email + permalink con reglas propias.
x-twitter-voice-style.md es OBLIGATORIO leer y aplicar antes de redactar x_twitter_ready.txt y threads_ready.txt (mismas reglas de tono y cadencia). Captura los anti-patrones de IA detectables al instante (em-dashes, "no es X, es Y", "la conclusión:", "lectura completa con datos verificados...", flechas → en listas, rule-of-three) y los patrones que SÍ funcionan: opener con dato raro/contradicción, hilos cortos (3-12 unidades de consideración), ritmo asimétrico (tuits de 30 y de 280 chars mezclados), castellano oral (contracciones, "o sea", "lo que pasa es que"), cierre human-first (pregunta abierta o link sin frase ceremoniosa). Aprendido tras publicar un hilo de 19 tuits que sonaba demasiado a IA (2026-05-24).
Scripts ejecutables (en el skill global)
| Script | Para qué | Cuándo invocarlo |
|---|---|---|
scripts/new-project.sh tema [--paste] |
Crea project_DDMMAAAA_tema/ con source.md + output/ + metadata.json |
Antes de cualquier proyecto nuevo |
scripts/metadata.py |
CRUD sobre metadata.json por proyecto (publications, metrics, tags) |
PASO 4 (set-generated) y PASO 5 (add-publication) |
scripts/validators/run_all.py |
Ejecuta los 4 validators (titles, numbers, catalan, blog completeness) | Hook PostToolUse después de cada Write/Edit en output/* |
scripts/publisher/wp_publish.py |
Publica blog en WordPress via REST API con Polylang single-call + Rank Math | PASO 5 cuando el cliente es zoopa.es / 498as.com |
Workspace project files
| Elemento | Ruta |
|---|---|
| System prompt | _system/system_prompt_writerbatch_zoopa.md (relativo al workspace contentfactory) |
| Source content | project-XXX/source.md (dentro del workspace) |
| Output dir | project-XXX/output/ |
| Metadata por proyecto | project-XXX/metadata.json (schema en scripts/metadata-schema.md) |
Cómo se invocan los scripts del skill global
Los scripts viven en el skill global pero operan sobre el workspace local del usuario (típicamente /Users/cop/Documents/claudecode-proj/contentfactory/). El usuario los invoca desde el workspace con paths absolutos:
# Crear proyecto:
~/.claude/skills/content-factory/scripts/new-project.sh tv-oscura --paste
# Validar un output:
python3 ~/.claude/skills/content-factory/scripts/validators/run_all.py project_XXX/output/blog.md
# Publicar:
python3 ~/.claude/skills/content-factory/scripts/publisher/wp_publish.py \
--file project_XXX/output/blog.md --lang es --categories 2245,2235 --status draft
O symlinkar al _system/ local para preservar el workflow histórico:
ln -sf ~/.claude/skills/content-factory/scripts _system
Setup walkthrough
Ver ~/.claude/skills/content-factory/docs/HOW-TO-NEW-PROJECT.md para el flujo completo paso a paso (incluye troubleshooting).
Flujo de Ejecucion
PASO 0: Selector de canales (OBLIGATORIO)
Antes de generar CUALQUIER contenido, preguntar al usuario:
"Que canales quieres incluir en esta campana?"
- Presentar la lista completa de canales (ver
references/channel-catalog.md) - Marcar los 12 canales core como activados por defecto
- Dejar que el usuario seleccione/deseleccione
- Confirmar seleccion antes de continuar
Total disponible: 35 canales | Core por defecto: 12 canales
Nunca asumir canales sin preguntar. Nunca generar contenido sin completar este paso.
PASO 1: Preguntas pre-publicacion (OBLIGATORIO)
Despues de seleccionar canales, preguntar SIEMPRE:
Web de publicacion — zoopa.es o 498as.com
Autor del blog — Carlos Ortet u otro autor disponible en WordPress
Canal de LinkedIn — ZOOPA, 498AS o Carlos Ortet personal
Tema nuevo o existente — Si ya hay posts anteriores, revisar para no duplicar y enlazar
Imagen principal — URL o ruta de la featured image (blog, Substack, Medium, LinkedIn, og:image)
Imagen secundaria — URL o ruta opcional para cuerpo del blog o plataformas con varias imagenes
⭐ Anecdotas reales con cliente nombrado — "¿Tienes 1-2 anecdotas reales de proyectos con clientes nombrados (con o sin NDA) que ilustren el angulo? Sin esto el post sale como divulgacion; con esto sale como posicionamiento." Si el usuario no tiene a mano, dejar placeholder visible en el
source.md(formato[EJEMPLO 1 — historia real ...]) y avisar antes de PASO 4.⭐ Si el autor es Carlos Ortet, añadir entrada a carlosortet.com (con pregunta previa) — Cuando se publique una pieza editorial (blog en zoopa.es / 498as.com, post en Medium, artículo en Harvard Deusto, Substack relevante, paper, etc.) firmada por Carlos Ortet, PREGUNTAR siempre al usuario despues de publicar: "¿Añadimos esta publicación a
carlosortet.com?". Si el usuario dice sí, ejecutar el sub-flujo de PASO 6.
Reglas derivadas
- Blog siempre primero. Su URL se integra despues en LinkedIn (CTA), Substack (enlace), WhatsApp (referencia), Medium/Dev.to/Hashnode (canonical URL) y cualquier plataforma que lo necesite.
- Titulos en sentence case. Solo la primera letra de la frase en mayuscula. Nombres propios y acronimos mantienen su capitalizacion. Aplica a todos los outputs en todos los idiomas.
- Verificar URLs de CTAs antes de meterlas. Hacer
curl -I(HTTP HEAD) a cada URL candidata de mid-CTA y final-CTA. Si 404, fallback al index del servicio o al contacto general. Verreferences/lessons-learned.md"CTAs URLs frágiles". - ⭐ Sin em-dash (
—), sin referencias temporales relativas, sin frases formulaicas de IA. Reglas globales enreferences/orthography-rules.md§ 3. Aplica a TODOS los canales (blog, LinkedIn, X, Substack, Medium, WhatsApp, Quora, Bluesky, Threads, etc.) y todos los idiomas. NO usar—ni–; NO usar "esta semana / este mes / este año / hoy / recientemente"; NO usar "También es, técnicamente, falsa" / "Lectura completa con datos verificados" / "La conclusión:" ni similares. Auditarlo en PASO 3.6 (revisión ortográfica final).
⭐ Validar la estructura del source.md antes de PASO 4
Verificar que el source.md cumple el formato 3-block descrito en references/content-brief-structure.md:
- Bloque 1 — Tesis (1 párrafo, 60-100 palabras, posición clara)
- Bloque 2 — Hechos curados (5-15 bullets, cada uno con dato + fuente + fecha)
- Bloque 3 — Ángulo de marca (3-5 bullets con anécdotas reales firmadas)
Si falta alguno, devolver al usuario para que lo complete. Sin las 3 partes, el output sale neutral.
PASO 2: Verificacion pre-launch
Antes de ejecutar, verificar que existen:
_system/system_prompt_writerbatch_zoopa.md— visible en el workspace actualproject-XXX/source.md— con el contenido completo (todos los items)project-XXX/output/— directorio de salida creadoproject-XXX/metadata.json— si no existe, crearlo conpython3 _system/metadata.py init project-XXX
Si el proyecto no existe, sugerir al usuario crearlo con ./_system/new-project.sh tema-corto (no crear carpetas a mano, el script genera la estructura completa con metadata.json inicial).
Si source.md esta vacio, no continuar — pedir al usuario que pegue el contenido.
Tras verificar, contar palabras del source y guardarlas: python3 _system/metadata.py set-source-words project-XXX --auto.
PASO 3: Generacion de contenido
- Leer el system prompt de
_system/system_prompt_writerbatch_zoopa.mdy adoptarlo como guia de voz, estilo y especificaciones por canal. - Leer todo el contenido de
project-XXX/source.mdcomo unico input autorizado. - Aplicar las reglas del system prompt:
- Todos los items de source.md son igualmente importantes
- Si hay multiples items, TODOS se integran en TODOS los outputs
- No ignorar ni descartar ninguna parte salvo instruccion explicita
- Generar outputs en este orden:
- Blog ES → Blog EN → Blog CA → Blog Lectura Facil (si aplica)
- LinkedIn ES → LinkedIn CA
- X/Twitter → Threads
- WhatsApp Grupos ES → CA → Canal ES → CA
- Substack → Medium
- Resto de canales seleccionados en orden alfabetico
- Idiomas: primero espanol, luego catalan e ingles segun canales seleccionados.
- Escribir cada output en
project-XXX/output/con el nombre de archivo del catalogo de canales. - No incluir razonamiento intermedio. Solo el contenido final en cada archivo.
⭐ Estilo específico por canal (OBLIGATORIO leer antes de redactar)
| Canal | Archivo de referencia | Cuándo se aplica |
|---|---|---|
linkedin.md / linkedin_ca.md |
references/linkedin-voice-carlos-ortet.md |
Cuenta personal de Carlos Ortet (LinkedIn personal) |
substack_ready.txt |
references/substack-style.md |
Siempre — Substack es email + permalink, no blog Wordpress condensado |
x_twitter_ready.txt / threads_ready.txt |
references/x-twitter-voice-style.md |
Siempre — X penaliza voz LLM, requiere cadencia humana asimétrica |
blog.md / blog_en.md / blog_ca.md |
references/blog-html-components.md + references/editorial-patterns.md |
Siempre — blog largo Wordpress con componentes HTML inline |
Regla crítica: si vas a generar substack_ready.txt, un output de LinkedIn personal de Carlos, o x_twitter_ready.txt / threads_ready.txt, lee primero el file de estilo correspondiente. Aplicar el estilo del blog largo a otros canales produce: (a) Substack → subject line aburrido + cuerpo con HTML que el editor Prosemirror rompe; (b) X → hilos formulaicos detectables como IA al instante (em-dashes, "no es X, es Y", "lectura completa con datos verificados", rule-of-three obsesivo).
⭐ Patrones editoriales obligatorios para blogs
Aplicar al menos 2 de 4 de los patrones documentados en references/editorial-patterns.md. Recomendado: los 4 si el contenido lo permite.
| Patrón | Cuándo aplicar | Output |
|---|---|---|
| 1. Killer line + pull-quote | Siempre (1 por blog) | Frase 15-25 palabras autocontenida, aislada en <blockquote> (componente "Pull-quote" de blog-html-components.md). Reusada textual en LinkedIn, X, Substack. |
| 2. Paradoja explícita | Cuando el ángulo identifica una decisión sectorial cuestionable | Subsección con estructura: A vs B opuestos → síntesis tipo "todo tiene que parecer lo que no es" |
| 3. "Cuándo X suma y cuándo X resta" | Siempre que la tesis ataque una práctica del sector | Subsección H3 que matiza, distinguiendo cuando la práctica es decisión deliberada útil vs cuando es reflejo/moda contraproducente |
| 4. Caso real con cliente nombrado | Siempre (1-2 casos) | Sección "Lo que vemos desde la trinchera" con anécdotas firmadas (clientes con o sin NDA). Estructura: tensión → decisión → lección |
⭐ Componentes HTML obligatorios en blogs
Incluir siempre estos componentes (definiciones en references/blog-html-components.md):
- Featured image figure inline al inicio del cuerpo (después del H1, antes de la intro)
- Stat block 2-column con 2-3 cifras de impacto del bloque 2 del source.md (gradient teal-marino)
- Key takeaways ("Lo que necesitas saber") con 5-7 bullets
- Mid-CTA con gradient charcoal hacia página de servicio (~50% del cuerpo)
- Pull-quote aislado con la killer line (antes del CTA final)
- Final-CTA negro sólido hacia /contactanos/ (más fuerte que el mid)
- Glosario de entidades (al final, antes del FAQ)
- FAQ ≥5 preguntas (al final)
PASO 3.5: Optimizacion SEO y GEO (OBLIGATORIO)
Despues de generar todos los outputs, aplicar las siguientes optimizaciones a TODOS los blogs (ES, EN, CA, ES Facil):
SEO
- Bloque de metadatos al inicio del archivo (como comentario HTML):
- Title tag (max 60 chars, con marca)
- Meta description (max 155 chars, con dato clave y CTA implicita)
- Slug optimizado (keywords separadas por guiones)
- Focus keyword y secondary keywords
- Canonical URL
- Category y tags
- Alt text descriptivo en todas las imagenes: no generico, sino que describa el contenido visual y contenga keywords relevantes
- Seccion "Lo que necesitas saber" (key takeaways) despues de la intro: 5-7 bullets con los datos mas citables del articulo. Optimizado para featured snippets de Google y para extraccion por LLMs
- Internal linking: enlazar a posts relacionados del mismo cluster/categoria Y a la pagina de servicio correspondiente (ver regla de CTAs abajo)
- CTAs contextuales (OBLIGATORIO): cada blog post debe incluir:
- Mid CTA (tras el ~50% del contenido): enlace a la pagina de servicio relacionada. Estilo: fondo gris, texto "Esto es lo nuestro →"
- Final CTA: enlace a /contactanos/. Estilo: fondo negro, texto "Hablemos"
- CTA de cluster (si aplica): enlace a la pillar page del cluster tematico. Ejemplo: posts GEO → /es/servicios/georadar/
- Heading hierarchy: exactamente 1 H1 (titulo del post). Subtitulos en H2. Sub-subtitulos en H3. Nunca saltar niveles.
- Meta description: maximo 155 caracteres. No repetir el nombre de marca si ya esta en el title tag.
- Alt text: descriptivo y con keywords, no generico. Cada imagen debe tener alt text unico.
GEO (Generative Engine Optimization)
- Glosario de entidades al final del articulo: definiciones claras y autonomas de cada termino tecnico, persona, herramienta u organizacion mencionada. Cada entrada debe ser citable de forma independiente por un LLM
- FAQ ampliado: minimo 5 preguntas, formuladas como long-tail keywords que un usuario buscaria. Respuestas directas en la primera frase, luego expansion
- Datos autonomos: cifras y estadisticas deben incluir fuente y fecha para maximizar citabilidad
- Entity-rich content: mencionar explicitamente nombres de empresas, herramientas, papers academicos y personas con contexto suficiente para que un LLM los identifique como entidades
- Links salientes E-E-A-T: incluir 2-4 links a fuentes autoritativas (papers academicos, documentacion oficial Google, instituciones como CSIC, UAB, universidades). Aumenta credibilidad para LLMs y Google
- Formato citable: parrafos cortos con afirmaciones autonomas que un LLM pueda extraer y citar directamente. Evitar contexto que requiera leer el parrafo anterior para entender
Estas optimizaciones aplican tambien a Medium, Dev.to, Hashnode y HackerNoon (sin bloque de metadatos WordPress, pero si con alt text, glosario y FAQ ampliado).
Publicacion del metadata en WordPress
Al publicar el blog en WordPress (zoopa.es o 498as.com), los metadatos del comentario HTML deben introducirse en los campos correspondientes del editor:
| Campo del comentario HTML | Donde introducirlo en WordPress |
|---|---|
| Title | Titulo del post (y campo SEO title en Yoast/RankMath) |
| Meta description | Campo "Meta description" en Yoast/RankMath |
| Slug | Campo "URL slug" / Permalink del post |
| Focus keyword | Campo "Focus keyphrase" en Yoast/RankMath |
| Canonical | Campo "Canonical URL" en Yoast (solo si es distinta de la URL del post) |
| Category | Selector de categorias de WordPress |
| Tags | Selector de etiquetas de WordPress |
Regla: El bloque de metadatos NO se publica como parte del contenido visible. Es una referencia para configurar los campos SEO del CMS. Al pegar el contenido en WordPress, eliminar el comentario HTML o asegurarse de que no se renderiza.
Maquetacion HTML avanzada para blogs
Los blogs (blog.md, blog_en.md, blog_ca.md) deben incluir maquetacion HTML profesional inline para que WordPress renderice tablas, key takeaways, glosarios y FAQs con diseno avanzado. Ver references/blog-html-components.md para los componentes disponibles.
Reglas de maquetacion:
- Tablas: nunca usar tablas Markdown planas. Usar tablas HTML con clases CSS inline que incluyan bordes, colores alternos, cabecera destacada y responsive design
- Key takeaways: usar un bloque HTML con fondo de color, borde lateral, icono y tipografia destacada
- FAQ: usar acordeones HTML/CSS o bloques con separadores visuales claros
- Glosario: usar bloques de definicion con tipografia jerarquizada (termino en bold/color, definicion en texto regular)
- Datos destacados: cifras importantes deben ir en bloques de estadistica con numero grande y fuente debajo
- Imagenes: deben ir dentro de
<figure>con<figcaption>descriptivo - Citas: usar
<blockquote>con estilo editorial (borde lateral, italica, fuente citada)
Todo el CSS debe ser inline (atributo style="") para garantizar compatibilidad con WordPress, newsletters y RSS. No depender de hojas de estilo externas ni de clases CSS del tema.
Consultar references/blog-html-components.md para copiar los componentes HTML exactos.
PASO 3.6: Revision ortografica y de estilo (OBLIGATORIO)
Despues de optimizar SEO/GEO y ANTES de dar los outputs por finalizados, ejecutar la revision completa descrita en references/orthography-rules.md.
Resumen:
- Acentos y caracteres especiales — Revisar cada output segun su idioma (ES: acentos/n/¿¡, CA: graves/agudos/c/l·l, EN: nombres propios)
- Capitalizacion sentence case — Verificar que todos los titulos, subtitulos y encabezados cumplen la regla
- Recorrer cada archivo, corregir y guardar
El proyecto NO se considera finalizado hasta completar los pasos 3.5 y 3.6.
PASO 4: Validacion de outputs
Verificar que project-XXX/output/ contiene EXACTAMENTE los archivos de los canales seleccionados en PASO 0. Ni mas ni menos.
Consultar references/channel-catalog.md para la tabla completa de archivos esperados por canal.
Validators automaticos
Cada vez que se hace Write/Edit sobre un fichero en project-XXX/output/, el hook PostToolUse de Claude Code (configurado en .claude/settings.json del workspace) ejecuta _system/validators/run_all.py. Esto produce avisos en stdout sobre:
- Sentence case en headings
- False friends
billion/billon(ES) obilions(CA) - Caracteres catalanes faltantes (ç, l·l) en archivos
*_ca.md - Completitud de blogs: bloque SEO, key takeaways, mid-CTA, final-CTA, glosario, FAQ ≥ 5, alt text, exactamente 1 H1, palabras minimas
Procedimiento: revisar los avisos despues de cada escritura y corregir antes de continuar al siguiente fichero. Los avisos NO bloquean, pero deben resolverse antes de PASO 5.
Ejecucion manual: python3 _system/validators/run_all.py project-XXX/output/blog.md.
Registrar canales generados
Tras escribir todos los outputs, marcar en metadata:
python3 _system/metadata.py set-generated project-XXX --channels blog_es,blog_en,blog_ca,linkedin_es,...
Convencion de keys: <canal_base>_<idioma> (ej: blog_es, linkedin_ca, whatsapp_canal_es). Ver _system/metadata-schema.md.
PASO 5: Publicacion en WordPress (si aplica)
Cuando el usuario pida publicar en el blog de zoopa.es o 498as.com, seguir este protocolo:
5.1 Publicar via API REST — CLI con curl/python (OBLIGATORIO)
NUNCA usar Playwright/MCP para operaciones de WordPress. Usar siempre la CLI (curl o python) con la API REST de WordPress. Es mas rapido, fiable y no sufre problemas de modales, timeouts ni snapshots enormes.
Autenticacion: Usar Application Password de WordPress o cookies de sesion reutilizadas.
# Subir imagen
curl -X POST "https://zoopa.es/wp-json/wp/v2/media" \
-u "usuario:app_password" \
-F "file=@/ruta/imagen.png" \
-F "alt_text=descripcion"
# Crear post
curl -X POST "https://zoopa.es/wp-json/wp/v2/posts" \
-u "usuario:app_password" \
-H "Content-Type: application/json" \
-d '{"title":"...","content":"...","status":"publish","categories":[ID],"author":ID,"lang":"es","featured_media":ID}'
# Actualizar post
curl -X POST "https://zoopa.es/wp-json/wp/v2/posts/ID" \
-u "usuario:app_password" \
-H "Content-Type: application/json" \
-d '{"content":"..."}'
Playwright SOLO se usa para plataformas sin API (LinkedIn, Substack, Threads).
POST /wp-json/wp/v2/posts/
Headers: Content-Type: application/json, X-WP-Nonce: wpApiSettings.nonce
Body: {title, content, status: "publish", categories: [...], featured_media: ID, lang: "es|ca|en"}
NUNCA usar el editor visual de Gutenberg para pegar contenido largo. Usar siempre la API REST o el editor de codigo.
5.2 Contenido en formato Gutenberg blocks
El contenido debe estar en formato de bloques Gutenberg (no Markdown ni HTML plano):
- Parrafos:
<!-- wp:paragraph --><p>...</p><!-- /wp:paragraph --> - Headings:
<!-- wp:heading --><h2>...</h2><!-- /wp:heading -->o con level:<!-- wp:heading {"level":3} --><h3>...</h3><!-- /wp:heading --> - Listas:
<!-- wp:list --><ul>...</ul><!-- /wp:list -->o<!-- wp:list {"ordered":true} --><ol>...</ol><!-- /wp:list --> - Tablas:
<!-- wp:table --><figure class="wp-block-table"><table>...</table></figure><!-- /wp:table --> - Imagenes:
<!-- wp:image --><figure class="wp-block-image"><img src="URL" alt="..."/></figure><!-- /wp:image --> - Separadores:
<!-- wp:separator --><hr class="wp-block-separator has-alpha-channel-opacity"/><!-- /wp:separator -->
5.3 Imagenes
- Subir TODAS las imagenes ANTES de crear los posts. Usar la pagina
/wp-admin/media-new.phpcon file upload via Playwright. - Obtener las URLs de las imagenes subidas via API:
GET /wp-json/wp/v2/media?per_page=N&orderby=date&order=desc - Reemplazar rutas locales por URLs de WordPress en el contenido ANTES de publicar.
- Imagen destacada: asignar via
featured_media: IDen el body del POST, no via el dialogo de WordPress (es poco fiable con Playwright). - Alt text: configurar al subir la imagen via la API:
POST /wp-json/wp/v2/media/IDcon{alt_text: "..."}.
5.4 Categorias por idioma (Polylang) — ACTUALIZADO ABRIL 2026
CRITICO: zoopa.es usa Polylang. Las categorias fueron reestructuradas en abril 2026.
Categorias ES (usar estas):
| Categoria | ID ES | Slug |
|---|---|---|
| GEO, IA y Visibilidad en Buscadores de IA | 2231 | geo-ia-visibilidad |
| Social Media | 2233 | social-media-es |
| Creatividad y Contenido | 2235 | creatividad-contenido |
| Innovacion y Tecnologia | 2237 | innovacion-tecnologia |
| Estrategia y Marketing Digital | 2239 | estrategia-marketing-digital |
| Branding, UX y Diseno | 2241 | branding-ux-diseno |
| Influencer Marketing | 2243 | influencer-marketing-es |
| Produccion Audiovisual | 2245 | produccion-audiovisual |
Reglas:
- Asignar 1-2 categorias tematicas por post. NO usar "Blog" ni "Sin categoria" (legacy).
- Todo post sobre GEO/ChatGPT/LLMs → obligatorio categoria 2231 (cluster estrategico con CTA automatico a /servicios/georadar/).
- Verificar IDs EN/CA antes de asignar traducciones:
GET /wp-json/wp/v2/categories?lang=en - Verificar que el post aparece en la pagina del blog tras publicar.
5.5 Autor
- Cambiar autor via API:
POST /wp-json/wp/v2/posts/IDcon{author: AUTHOR_ID} - Carlos Ortet: verificar ID con
GET /wp-json/wp/v2/users?search=Carlos - Alternativa: usar el dialogo de autor en el editor de WordPress.
5.6 Rank Math SEO
Configurar via la API de Rank Math:
POST /wp-json/rankmath/v1/updateMeta
Body: {
objectID: POST_ID,
objectType: "post",
meta: {
rank_math_title: "...",
rank_math_description: "...",
rank_math_focus_keyword: "..."
}
}
Usar los datos del bloque de metadatos HTML del archivo .md generado en PASO 3.5.
5.7 Traducciones (Polylang)
Para vincular posts como traducciones entre idiomas:
POST /wp-json/wp/v2/posts/POST_ID
Body: {lang: "ca", translations: {es: ES_POST_ID, ca: CA_POST_ID}}
IMPORTANTE: Asignar idioma y traducciones en una SOLA operacion. Hacerlo por separado puede vaciar el contenido del post (bug observado en Polylang via API REST).
5.8 Verificacion post-publicacion
Despues de publicar, verificar SIEMPRE:
- Contenido:
GET /wp-json/wp/v2/posts/ID?context=edit→content.raw.length > 0 - Imagen destacada:
featured_media > 0 - Alt text: verificar que la imagen destacada tiene alt text descriptivo con keywords
- Categorias correctas: 1-2 categorias tematicas de las 8 nuevas (no legacy)
- Visibilidad en la pagina del blog: navegar a la pagina del blog del idioma y confirmar que el post aparece
- Traducciones vinculadas: verificar que
translationscontiene los IDs correctos - OG:image: verificar que el post tiene imagen para compartir en redes. Si no tiene featured image, asignar una.
- Heading hierarchy: verificar que el post tiene exactamente 1 H1 (titulo) y que no hay saltos de nivel
- Meta description: verificar que tiene ≤155 caracteres y no esta vacia
- CTAs presentes: verificar que el contenido incluye mid-CTA (→ servicio) y final-CTA (→ contacto)
Si alguna verificacion falla, corregir inmediatamente via la API REST antes de continuar.
5.9 Interlinking y clusters (OBLIGATORIO)
Despues de publicar, ejecutar estas acciones de interlinking:
Enlazar a pagina de servicio: todo post debe enlazar a la pagina de servicio mas relevante de zoopa.es. Servicios disponibles:
- /es/servicios/georadar/ (GEO)
- /es/servicios/servicios-geo-visibilidad-en-ia-chatgpt/ (GEO/IA)
- /es/servicios/marketing-de-influencers/
- /es/servicios/www-zoopa-com-social-media/
- /es/servicios/produccion-y-branded-content/
- /es/servicios/agencia-creativa/
- /es/servicios/branding-diseno-grafico-identidad-visual/
- /es/servicios/estrategia/
- /es/servicios/agencia-paidmedia-performance-marketing/
- /es/servicios/zoopa-agency-gestion-linkedin/
- /es/servicios/wikipedia-reputacion-online/
- /es/servicios/servicios-agencia-reddit/
- /es/servicios/agencia-pinterest-para-empresas/
- /es/servicios/diseno-produccion-eventos/
Posts GEO → pillar automatico: los posts en categoria GEO (2231) reciben automaticamente un CTA a /es/servicios/georadar/ via Snippet #56. No es necesario anadir el link manualmente, pero si es bueno incluir un link contextual inline adicional.
Enlazar a posts relacionados: buscar 2-3 posts del mismo cluster/categoria y enlazarlos inline en el contenido. Consultar:
GET /wp-json/wp/v2/posts?categories=CAT_ID&per_page=5&exclude=POST_IDPublicar en 3 idiomas: todo post de zoopa.es debe existir en ES, EN y CA con traducciones vinculadas en Polylang. El contenido EN y CA puede ser mas corto pero debe cubrir los puntos clave.
5.10 Registrar publicaciones en metadata.json (OBLIGATORIO)
Despues de cada publicacion exitosa, anadir el registro al metadata.json del proyecto. Esto permite analisis transversal posterior (que canales, que rendimiento, que clusters).
# Blog WordPress
python3 _system/metadata.py add-publication project-XXX \
--channel blog_es \
--url https://zoopa.es/blog/slug-del-post \
--wp-post-id 12345 \
--categories 2231 \
--featured-media 9876
# Canal social (LinkedIn, Substack, Medium, Threads, etc.)
python3 _system/metadata.py add-publication project-XXX \
--channel linkedin_es \
--url https://linkedin.com/posts/... \
--account "Carlos Ortet"
Hacer una llamada por canal publicado. Si un canal no se llega a publicar (p.ej. Hashnode falla), no registrarlo — solo published[<canal>] refleja publicaciones reales.
Ver _system/metadata-schema.md para el schema completo y la convencion de keys de canal.
PASO 6: Añadir entrada a carlosortet.com (CONDICIONAL — solo si autor = Carlos Ortet)
OBLIGATORIO preguntar al usuario despues de la publicacion exitosa de cualquier pieza editorial firmada por Carlos Ortet (blog en zoopa.es / 498as.com, post en Medium, articulo en Harvard Deusto Business Review, Substack relevante, paper academico, etc.).
Pregunta exacta:
"He publicado [TITULO] en [PLATAFORMAS]. ¿Añadimos esta publicacion a la lista de carlosortet.com? Va a
src/data/resume.tsxarraypublications, incluyendo botones a las versiones disponibles (EN/ES/CA + Medium si aplica)."
Opciones a ofrecer:
- Si — commit + push directo a
main(recomendado, deploy automatico Vercel/Netlify) - Solo commit local, no push
- Crear branch + PR (si requiere revision previa)
- No, no procede
6.1 Estructura de la entrada (TypeScript)
Insertar como primer item del array publications (mas reciente primero) en /Users/cop/Documents/03_PERSONAL/carlosortet/src/data/resume.tsx. Formato:
{
title: "<titulo de la pieza, version EN si existe>",
dates: "<Mes Año en EN>", // Ej: "May 2026"
location: "<plataforma principal · plataformas secundarias>", // Ej: "Medium · zoopa.es (EN, ES, CA)"
description:
"<150-300 palabras EN, datos verificables clave, sin pitch comercial>",
image: "",
links: [
{
title: "Read in English (Zoopa)",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://zoopa.es/en/<category-slug>/<post-slug>/",
},
{
title: "Read in Spanish (Zoopa)",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://zoopa.es/es/<category-slug>/<post-slug>/",
},
{
title: "Read in Catalan (Zoopa)",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://zoopa.es/ca/<category-slug>/<post-slug>/",
},
{
title: "Read on Medium",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://medium.com/@carlosortet/<slug>",
},
],
},
Reglas:
- Incluir solo los
linksque existen realmente (verificar URLs activas antes concurl -I). - Idioma
description: SIEMPRE en EN (la home del site esta en EN). datesen formato "May 2026" (en EN).- Mantener el patron de commits existente:
publications · Add '<titulo corto>' (<plataforma principal> · <fecha>).
6.2 Flujo operativo
# 1. Editar el archivo
# (con Edit tool, insertar como primer item del array publications)
# 2. Verificar el diff
cd /Users/cop/Documents/03_PERSONAL/carlosortet
git diff src/data/resume.tsx
# 3. Commit con mensaje en patron existente
git add src/data/resume.tsx
git commit -m "publications · Add '<TITULO>' (<PLATAFORMA> · <FECHA>)"
# 4. Push a main (si autorizado)
git push origin main
# 5. Esperar ~60-90s deploy Vercel/Netlify
# 6. Verificar en vivo
curl -s -H "User-Agent: Mozilla/5.0" https://carlosortet.com | grep -oE "<titulo distintivo>" | head -1
6.3 Caso de estudio (mayo 2026)
Ejemplo real en commit 8a28b60: tras publicar "The invisible fabric of AI" (Medium + zoopa.es EN/ES/CA), se añadio entrada con los 4 links. Deploy completado en ~2 min, verificado via grep al HTML de produccion.
Recomendaciones por tipo de producto
| Tipo | Plataformas recomendadas |
|---|---|
| Thought leadership / Analisis | Blog, LinkedIn, Medium, Substack, HN, Threads |
| Open Source | Reddit, Dev.to, Hashnode, HN, Lobsters, Mastodon |
| SaaS / Commercial | G2, Capterra, Product Hunt, Trustpilot |
| Consumer App | Product Hunt, App Store, Trustpilot, Threads |
| Enterprise | G2, Capterra, Gartner Peer Insights, DZone |
| Web3 / Crypto | Mirror.xyz, Farcaster, Reddit crypto subs |
Referencias
- Ver
references/channel-catalog.mdpara la lista maestra de 35 canales con archivos, idiomas y defaults - Ver
references/orthography-rules.mdpara las reglas de revision ortografica y de estilo - Ver
references/lessons-learned.mdpara lecciones aprendidas por plataforma