Table of Contents
- Introducción: el backend es ahora una conversación
- ¿Qué es esta "habilidad Directus" de todos modos
- Requisitos previos: lo que necesita antes de comenzar
- Instalación de la habilidad en Hermes Agent
- Anatomía de una especificación de backend de un solo mensaje
- Ejemplo 1: Backend de blog (preparado para varios idiomas)
- Ejemplo 2: backend de comercio electrónico en un solo mensaje
- Ejemplo 3: CRM simple (Contactos + Empresas + Interacciones)
- Cómo funciona realmente: la salsa secreta de la habilidad
- Patrones probados en producción que hemos descubierto
- 1. El esquema y el meta son obligatorios
- 2. Utilice get_expanded() para relaciones
- 3. Los ID son cadenas en GraphQL, enteros en REST
- 4. Las escrituras de la tabla de conexiones revisan la colección de conexiones
- 5. Las colecciones de traducción necesitan inserciones de dos pasos
- 6. El error de truncamiento POST y la solución PATCH
- 7. Verifique el acceso de escritura antes de publicar
- Manejo de errores: qué sale mal y cómo solucionarlo
- Patrones avanzados: filtros, relaciones y campos de enumeración
- La API de administración: cuando necesita tocar el esquema mediante programación
- Poniéndolo todo junto: su primera sesión de Vibe-Code
- Qué permite esto: creación rápida de prototipos a escala
- Limitaciones y trampas
- Conclusión: el backend es ahora una conversación
- Próximos pasos
Introducción: el backend es ahora una conversación
!Vibe-coding workflow diagram: natural language prompt to Directus schema to production API
Aquí hay un patrón frustrante que la mayoría de los desarrolladores conocen bien:
- Tienes una idea brillante para una aplicación.
- Comienzas a crear andamios en la base de datos:
usuarios,publicaciones,órdenes - Usted construye la capa API: puntos finales REST, solucionadores GraphQL
- Conectas autenticación, permisos y validación.
- Tres días después, finalmente llegas a la función real que querías crear
Esa fricción es real. Y en 2026, estará muerto.
¿Qué pasaría si pudieras omitir los pasos 2 a 4 por completo? ¿Qué pasaría si pudiera describir todo su backend en inglés sencillo, presionar Intro y tener un esquema, una API y un panel de administración listos para producción?
Eso es lo que significa codificar un backend por vibración. Y con Directus, el CMS headless de código abierto que convierte su base de datos en una API instantánea, junto con la nikola66 Directus skills (un cliente GraphQL universal para agentes de IA), ahora es posible crear una capa de datos completa de una sola vez.
Esta no es una demostración de juguete. Este es un flujo de trabajo real que estamos utilizando en Ainex para poner en marcha los backends del proyecto en minutos, no en días. En esta guía, aprenderá:
- Qué hace realmente la habilidad Directus bajo el capó
- Cómo instalarlo y conectarse a su instancia de Directus
- La anatomía de una especificación de backend de un solo mensaje
- Ejemplos paso a paso: creación de un blog, una tienda de comercio electrónico y un CRM
- Patrones de producción, manejo de errores y trampas que ya hemos encontrado en la naturaleza.
- Cómo verificar que su backend recientemente estructurado esté activo y sea consultable
Entremos en ello.
¿Qué es esta "habilidad Directus" de todos modos?
La habilidad en nikola66/directus-skill es un cliente Python que habla directamente las API GraphQL y REST de Directus. Está diseñado para agentes de IA como Hermes, pero también puedes usarlo como una biblioteca independiente.
Propiedades clave que hacen posible la codificación por vibración:
- Independiente de la colección: No importa si estás trabajando con
Blog_PostsoEcom_Products. Los mismos métodos funcionan en cualquier colección. - Superficie CRUD completa:
get,insert,update,delete,counte inclusocreate_collectionycreate_fieldpara operaciones de esquema (solo administrador). - Expansión de relación inteligente: utilice notación de puntos como
autor.nombreoorder_items.product.titley la habilidad creará los conjuntos de selección GraphQL anidados correctos. - Operadores de filtro: todos los filtros Directus compatibles:
_eq,_contains,_in,_gt,_null, etc. - Manejo de errores: Genera
DirectusErrorcon detalles estructurados para que sepas exactamente por qué algo falló.
Cuando carga esta habilidad en un agente de IA, le brinda a ese agente la capacidad de iniciar un esquema de base de datos completo a través de una conversación.
Requisitos previos: lo que necesita antes de comenzar
Antes de poder codificar un backend, necesitas tres cosas:
- Una instancia de Directus: ya sea autohospedada o Directus Cloud. Corremos en
https://hub.aratech.ae. - Un token API con los permisos adecuados. Para la creación de esquemas, necesitará un token de administrador. Consíguelo en Directus → Configuración → Tokens API → Crear (ámbito de administrador, todos los permisos).
- Python 3.9+ con la habilidad instalada:
# Opción A: Instalar desde PyPI (cuando se publique)
pip instalar directus-habilidad
## Opción B: Clonar y usar directamente (actual)
clon de git https://github.com/nikola66/directus-skill.git
## Copie el paquete en su proyecto o agréguelo a sys.pathA partir de abril de 2026, la habilidad se mantiene activamente y se valida en producción frente a una instancia de Directus 10+ con CRUD completo, operadores de filtro y gestión de colecciones funcionando de manera confiable.
Instalación de la habilidad en Hermes Agent
Si está ejecutando Hermes Agent (nuestra plataforma de orquestación de múltiples agentes), agregar la habilidad es un solo comando:
habilidades de hermes instalar directoO para agregar la versión ascendente de GitHub directamente:
Las habilidades de npx agregan nikola66/directus-skillUna vez instalada, la habilidad directus se carga automáticamente cuando preguntas sobre las operaciones de Directus. No es necesario importar nada manualmente: el agente se encarga de eso.
Configure sus variables de entorno (las mantenemos en ~/.bashrc o la sesión de puerta de enlace):
exportar DIRECTUS_URL="https://hub.aratech.ae"
exportar DIRECTUS_API_TOKEN="user_token_here" # operaciones CRUD
export DIRECTUS_ADMIN_TOKEN="admin_token_here" # Gestión de esquemasNota: En la puerta de enlace de Hermes, estos se inyectan automáticamente desde la configuración de la puerta de enlace. Si está ejecutando secuencias de comandos independientes, configúrelas usted mismo.
Anatomía de una especificación de backend de un solo mensaje
La magia ocurre cuando describe todo su modelo de datos en un mensaje de lenguaje natural. Aquí está el patrón:
Constrúyeme un backend [TIPO] con Directus. Crea estas colecciones:
1. [Nombre de la colección] - [propósito]
- nombre_campo (tipo), campo2 (tipo), ...
- relaciones: [detalles de la relación]
2. [Nombre de la colección] - ...
...
Después de crear todas las colecciones:
- Verifique llamando a get_collections() e imprimiendo la lista
- Semillas [N] registros de muestra para cada colección
- Generar el script Python completoEl agente interpreta esto, lo traduce en llamadas a la API de administración de Directus ("POST /collections" con definiciones de campo completas), lo ejecuta y devuelve un esquema de trabajo.
La habilidad hace el trabajo pesado:
- Crea la estructura de carga útil correcta para los peculiares requisitos de "esquema" y "meta" de Directus.
- Llama a
create_collection()por colección con la lista de campos completa - Opcionalmente, genera datos de muestra mediante operaciones CRUD.
- Verifica el éxito con
get_collections()
Ejecución todo en uno, sin intervención manual.
Ejemplo 1: Backend de blog (preparado para varios idiomas)
Comencemos con un clásico: un blog compatible con 5 idiomas. Aquí está el mensaje completo que le daría al agente:
Constrúyeme un backend de blog multilingüe con Directus. Crea estas colecciones:
1. Blog_Authors - perfiles de autor
- nombre (cadena), slug (cadena, único), biografía (texto), avatar (archivo), social_twitter (cadena, anulable)
- estado: borrador/publicado
- Campos: id, creado_at, actualizado_at
2. Blog_Categories: taxonomía jerárquica
- nombre (cadena), slug (cadena, único), descripción (texto), parent_category (varios a uno → Blog_Categories, anulable)
- icono (cadena, nombre del icono), color (cadena, hexadecimal)
- Campos: id, sort_order
3. Blog_Tags: etiquetas planas
- nombre (cadena), slug (cadena, único), color (cadena, hexadecimal)
- Campos: identificación
4. Blog_Posts: registros principales (solo metadatos, según nuestro diseño de esquema)
- estado (borrador/publicado), publicado_en (fecha y hora, anulable)
- autor (varios a uno → Blog_Authors), categoría (varios a uno → Blog_Categories)
- imagen_presentada (archivo), minutos_tiempo_lectura (entero)
- is_featured (booleano), is_pillar (booleano)
- Campos: id, creado_at, actualizado_at
5. Blog_Posts_Translations: contenido por idioma (registros secundarios)
- blog_posts_id (entero FK → Blog_Posts), idiomas_code (cadena: en/ar/de/es/fr)
- título (cadena), slug (cadena), contenido (texto de rebajas)
- extracto (texto, máximo 200 caracteres), key_takeaways (matriz json, 3-5 elementos)
- meta_title (cadena, ≤60), meta_description (cadena, ≤160)
-feature_image_alt (cadena)
- cta_headline (cadena), cta_button_text (cadena), cta_button_url (cadena)
- esquema_markup (json - Artículo esquema.org)
- Campos: identificación
6. Blog_Posts_Tags: unión para publicaciones de muchos a muchos ↔ etiqueta
- blog_posts_id (FK → Blog_Posts), blog_tags_id (FK → Blog_Tags)
- Campos: identificación
Importante:
- Utilice create_collection() para las 6 colecciones.
- Cada campo debe incluir esquema: {} y meta: {} en la carga útil
- Defina TODOS los campos por adelantado (no se agregan campos incrementales)
- Después de crear colecciones, genere un mínimo de 2 publicaciones de blog de muestra:
* Publicación 1: "Introducción a la seguridad de la IA" - autor=1, categoría=33 (IA y LLM), publicada
* Publicación 2: "Comprensión del cumplimiento de SOC 2" - autor=1, categoría=37 (Cumplimiento), borrador
* Para cada publicación, cree solo traducción EN (omita AR/DE/ES/FR por ahora)
- Verificación de impresión: enumera todas las colecciones y sus recuentos de registros¿Qué hace esto?
Cuando ejecuta este mensaje a través de un agente con la habilidad Directus cargada:
- Crea 6 colecciones con definiciones de campo completas en el orden correcto (principalmente padres, luego uniones, luego traducciones)
- Agrega relaciones adecuadas - Muchos a uno desde Publicaciones → Autores/Categoría, Muchos a muchos a través de una tabla de unión
- Respeta el diseño de su esquema real - Los registros de traducción son una colección secundaria separada vinculada por
blog_posts_id - Semillas de datos reales: dos publicaciones de muestra con los campos obligatorios completados
- Verifica - Imprime lo que se creó
El código generado (lo que se ejecuta bajo el capó)
El agente produce algo como este script de Python:
desde directus.client importar DirectusClient
importar sistema operativo
cliente = DirectusCliente(
url=os.getenv('DIRECTUS_URL'),
token=os.getenv('DIRECTUS_ADMIN_TOKEN') # token de administrador para operaciones de esquema
)
## 1. Blog_Autores
client.create_collection('Blog_Authors', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'campo': 'nombre', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'field': 'slug', 'type': 'string', 'schema': {'is_unique': True}, 'meta': {'interface': 'input'}},
{'campo': 'bio', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'textarea'}},
{'campo': 'avatar', 'tipo': 'archivo', 'esquema': {}, 'meta': {'interfaz': 'archivo', 'opciones': {'accepted_files': 'image/*'}}},
{'campo': 'social_twitter', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'estado', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'seleccionar', 'opciones': {'opciones': ['borrador', 'publicado']}}},
{'field': 'created_at', 'type': 'timestamp', 'schema': {}, 'meta': {'interface': 'datetime', 'readonly': True}},
{'campo': 'updated_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## 2. Blog_Categorías
client.create_collection('Categorías_Blog', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'campo': 'nombre', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'field': 'slug', 'type': 'string', 'schema': {'is_unique': True}, 'meta': {'interface': 'input'}},
{'campo': 'descripción', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'textarea'}},
{'field': 'parent_category', 'type': 'integer', 'schema': {}, 'meta': {'interface': 'relation', 'options': {' related_collection': 'Blog_Categories'}}},
{'campo': 'icono', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'color', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'color'}},
{'field': 'sort_order', 'type': 'integer', 'schema': {}, 'meta': {'interface': 'numeric'}},
{'field': 'created_at', 'type': 'timestamp', 'schema': {}, 'meta': {'interface': 'datetime', 'readonly': True}},
{'campo': 'updated_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## 3. Etiquetas de blog
client.create_collection('Blog_Tags', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'campo': 'nombre', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'field': 'slug', 'type': 'string', 'schema': {'is_unique': True}, 'meta': {'interface': 'input'}},
{'campo': 'color', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'color'}},
{'campo': 'created_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## 4. Blog_Posts: registro de metadatos principal
client.create_collection('Blog_Posts', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'campo': 'estado', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'seleccionar', 'opciones': {'opciones': ['borrador', 'publicado']}}},
{'field': 'published_at', 'type': 'timestamp', 'schema': {}, 'meta': {'interface': 'datetime', 'readonly': False}},
{'campo': 'autor', 'tipo': 'entero', 'esquema': {}, 'meta': {'interfaz': 'relación', 'opciones': {' related_collection': 'Blog_Authors'}}},
{'campo': 'categoría', 'tipo': 'entero', 'esquema': {}, 'meta': {'interfaz': 'relación', 'opciones': {' related_collection': 'Blog_Categories'}}},
{'campo': 'featured_image', 'tipo': 'archivo', 'esquema': {}, 'meta': {'interfaz': 'archivo', 'opciones': {'accepted_files': 'imagen/*'}}},
{'campo': 'reading_time_ Minutes', 'tipo': 'entero', 'esquema': {}, 'meta': {'interfaz': 'numérico'}},
{'campo': 'is_featured', 'tipo': 'booleano', 'esquema': {}, 'meta': {'interfaz': 'booleano'}},
{'campo': 'is_pillar', 'tipo': 'booleano', 'esquema': {}, 'meta': {'interfaz': 'booleano'}},
{'field': 'created_at', 'type': 'timestamp', 'schema': {}, 'meta': {'interface': 'datetime', 'readonly': True}},
{'campo': 'updated_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## 5. Blog_Posts_Translations: contenido por idioma
client.create_collection('Blog_Posts_Translations', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'field': 'blog_posts_id', 'type': 'integer', 'schema': {}, 'meta': {'interface': 'relation', 'options': {' related_collection': 'Blog_Posts'}}},
{'field': 'languages_code', 'type': 'string', 'schema': {}, 'meta': {'interface': 'select', 'options': {'choices': ['en', 'ar', 'de', 'es', 'fr']}}},
{'campo': 'título', 'tipo': 'cadena', 'esquema': {'is_required': Verdadero}, 'meta': {'interfaz': 'entrada'}},
{'field': 'slug', 'type': 'string', 'schema': {'is_required': True, 'is_unique': True}, 'meta': {'interface': 'input'}},
{'campo': 'contenido', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'markdown'}},
{'campo': 'extracto', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'textarea'}},
{'campo': 'key_takeaways', 'tipo': 'json', 'esquema': {}, 'meta': {'interfaz': 'lista'}},
{'campo': 'meta_title', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'meta_descripción', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'textarea'}},
{'campo': 'featured_image_alt', 'tipo': 'texto', 'esquema': {}, 'meta': {'interfaz': 'textarea'}},
{'campo': 'cta_headline', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'cta_button_text', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'cta_button_url', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'schema_markup', 'tipo': 'json', 'esquema': {}, 'meta': {'interfaz': 'json'}},
{'field': 'created_at', 'type': 'timestamp', 'schema': {}, 'meta': {'interface': 'datetime', 'readonly': True}},
{'campo': 'updated_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## 6. Blog_Posts_Tags - tabla de unión
client.create_collection('Blog_Posts_Tags', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'field': 'blog_posts_id', 'type': 'integer', 'schema': {}, 'meta': {'interface': 'relation', 'options': {' related_collection': 'Blog_Posts'}}},
{'field': 'blog_tags_id', 'type': 'integer', 'schema': {}, 'meta': {'interface': 'relation', 'options': {' related_collection': 'Blog_Tags'}}},
{'campo': 'created_at', 'tipo': 'marca de tiempo', 'esquema': {}, 'meta': {'interfaz': 'fecha y hora', 'solo lectura': Verdadero}}
])
## Datos de muestra de semillas
## 1. Crear autor (si no existe)
autor = client.insert({'nombre': 'Alex Chen', 'slug': 'alex-chen', 'bio': 'Ingeniero de seguridad en Ainex.', 'status': 'publicado'}, colección = 'Blog_Authors')
autor_id = autor['id']
## 2. Crear categoría
categoría = client.insert({'nombre': 'AI y LLM', 'slug': 'ai-llms', 'descripción': 'Artículos sobre seguridad de IA y LLM.', 'sort_order': 1}, colección = 'Blog_Categories')
categoría_id = categoría['id']
## 3. Crear un registro de publicación de blog para padres
publicación = cliente.insertar({
'estado': 'publicado',
'autor': autor_id,
'categoría': categoría_id,
'tiempo_lectura_minutos': 8,
'is_featured': Falso,
'publicado_en': '2026-05-03T10:00:00.000Z'
}, colección='Blog_Posts')
post_id = publicación['id']
## 4. Crear traducción al inglés
cliente.insertar({
'blog_posts_id': post_id,
'languages_code': 'es',
'title': 'Introducción a la seguridad de la IA',
'slug': 'introducción-a-la-seguridad-ai',
'content': '# Fundamentos de seguridad de IA\n\nEste es un artículo de muestra sobre cómo proteger los sistemas de IA...\n\n## Riesgos clave\n\n- Inyección rápida\n- Fuga de datos\n- Envenenamiento de modelos\n\n## Mejores prácticas\n\nSiempre valide las entradas, la ejecución del modelo de espacio aislado y monitoree las anomalías.',
'excerpt': 'Una guía para principiantes para comprender y mitigar los riesgos del sistema de IA.',
'key_takeaways': ['Los sistemas de IA son vulnerables a ataques de inyección rápida', 'Validar y desinfectar siempre las entradas del modelo', 'Monitorear el comportamiento anómalo del modelo'],
'meta_title': 'Introducción a la Seguridad de la IA - Guía Ainex',
'meta_description': 'Aprenda los fundamentos de la seguridad de la IA, las amenazas comunes y las mejores prácticas para proteger sus aplicaciones LLM.',
'featured_image_alt': 'Visualización abstracta de la seguridad de las redes neuronales',
'cta_headline': '¿Listo para proteger tu IA?',
'cta_button_text': 'Comenzar',
'cta_button_url': '/empezar',
'schema_markup': {
'@context': 'https://schema.org',
'@tipo': 'Artículo',
'headline': 'Introducción a la seguridad de la IA',
'autor': {'@tipo': 'Persona', 'nombre': 'Alex Chen'},
'fecha de publicación': '2026-05-03T10:00:00.000Z',
'description': 'Una guía para principiantes para comprender y mitigar los riesgos del sistema de IA.'
}
}, colección='Blog_Posts_Translations')
print(f"✅ ¡Blog backend creado! ID de publicación: {post_id}")Ese script lo genera y ejecuta el agente en un solo turno. Obtiene un esquema completo y consultable y datos inicializados de inmediato.
Ejemplo 2: backend de comercio electrónico en un solo mensaje
Vamos a ser más grandes. Así es como se estructuraría una capa de datos de comercio electrónico completa:
Cree un backend de comercio electrónico de producción con Directus. Crea estas colecciones:
1. **Productos** - artículos del catálogo
- título, slug (único), descripción (texto), estado (borrador/disponible/agotado)
- price_cents (entero), compare_price_cents (entero anulable), cost_cents (entero)
- sku (único), código de barras (anulable), peso_gramos (entero)
- cantidad_inventario, umbral_bajo_stock (predeterminado 10)
- feature_image (archivo), imágenes (M2M a través de unión)
- categoría (M2O → Categorías), etiquetas (M2M → Etiquetas vía cruce)
- publicado_en
2. **Categorías** - taxonomía anidada
- nombre, slug (único), descripción, parent_category (m2o anulable)
- imagen (archivo), sort_order
3. **Etiquetas** - etiquetas de productos
- nombre, babosa (única), color (hexadecimal)
4. **Clientes** - cuentas
- correo electrónico (único), nombre, apellido, teléfono
- dirección_de_envío_predeterminada (json), dirección_de_facturación_predeterminada (json)
- total_spent_cents, order_count, last_order_at (anulable)
- estado (activo/bloqueado), notas
5. **Pedidos** - transacciones
- número de pedido (único), estado (pendiente/procesando/enviado/entregado/cancelado)
- cliente (M2O → Clientes)
- subtotal_cents, tax_cents, envío_cents, total_cents, descuento_cents
- dirección_envío, dirección_facturación (json)
- estado_pago (pagado/reembolsado/fallido), método_pago (stripe/paypal/manual)
- Payment_intent_id (anulable), número_seguimiento (anulable)
- notas, cancelled_at (anulable)
6. **Order_Items** - artículos de línea (tabla de unión Pedidos ↔ Productos)
- pedido (M2O → Pedidos), producto (M2O → Productos)
- cantidad, centavos de precio unitario, centavos de precio total
- nombre_variante (cadena, anulable)
7. **Ajustes_de_inventario** - registro de auditoría
- producto (M2O → Productos), motivo (reposición/venta/devolución/auditoría/ajuste)
- cantidad_delta, cantidad_anterior, cantidad_nueva
- reference_id (anulable - enlaces a pedido/ajuste), notas
8. **Colecciones**: agrupaciones seleccionadas
- nombre, slug (único), identificador (cadena compatible con API)
- tipo (manual/automático), reglas (json para reglas automáticas)
- sort_order, publicado (booleano)
9. **Descuentos** - códigos promocionales
- código (único, anulable para automático), tipo (porcentaje/fijo)
- valor (int - porcentaje o centavos), max_uses, used_count
- valid_from, valid_until (anulable)
- se aplica_a (todos/categorías/productos), aplicables_producto_ids (matriz json)
- min_order_amount_cents (anulable), excluir_sale_items (booleano)
Restricciones:
- Utilice create_collection() para las 9 colecciones + tablas de unión explícitas (Products_Images si es necesario)
- Cada campo: incluya esquema: {} y meta: {} al principio
- Utilice los tipos de campo Directus adecuados: entero, cadena, texto, booleano, fecha y hora, json, archivo, pivote
- Agregar índices únicos en slug, sku, número_pedido
- Semilla: cree 3 productos de muestra con imágenes de muestra (use ID de archivo si están disponibles), enlace a la categoría
- Verificar: llame a get_collections() e imprima el recuento de registros
- Generar el script Python completoAl ejecutar esto a través del agente se produce un script de aproximadamente 300 líneas que crea todo el modelo de datos de comercio electrónico de una sola vez. De cero a "aquí está tu panel de administración" antes de que se enfríe el café.
Ejemplo 3: CRM simple (Contactos + Empresas + Interacciones)
¿Necesitas algo liviano? Aquí hay un CRM:
Cree un backend de CRM con Directus:
1. Empresas
- nombre, dominio (único), industria, tamaño (1-10/11-50/51-200/200+)
- sitio web, teléfono, dirección (json), ingresos_anuales_centavos (anulable)
- propietario (M2O → Usuarios), estado (cliente potencial/cliente/prospecto/desechado)
- last_contacted_at, notas (texto)
2. Contactos
- nombre, apellido, correo electrónico (único), teléfono, puesto de trabajo
- empresa (M2O → Empresas), propietario (M2O → Usuarios)
- estado (activo/inactivo/cliente potencial), contacto_preferido (correo electrónico/teléfono/linkedin)
- linkedin_url, notas
3. Interacciones
- contacto (M2O → Contactos), tipo (llamada/correo electrónico/reunión/nota)
- dirección (entrante/saliente), duración_minutos (entero)
- resumen (texto), próximos_pasos (texto), fecha_de_vencimiento (anulable)
- related_deal (M2O → Ofertas anulables)
4. Ofertas
- título, valor_centavos, etapa (propuesta/negociación/ganado_cerrado/perdido_cerrado)
- probabilidad (0-100), fecha_de_cierre (esperada)
- contacto (M2O → Contactos), propietario (M2O → Usuarios)
- notas
Restricciones:
- Utilice create_collection() para los 4
- Todos los campos estándar: id, creado_at, actualizado_at agregados automáticamente
- Relaciones configuradas correctamente (los FK son números enteros)
- Semilla: 2 empresas, 4 contactos, 3 interacciones, 1 tratoNuevamente: un mensaje, una ejecución.
Cómo funciona realmente: la salsa secreta de la habilidad
Desmitifiquemos lo que sucede cuando le pide al agente que "construya un backend":
Paso 1: el agente analiza su mensaje
El agente utiliza su razonamiento para extraer:
- Nombres de las colecciones y su finalidad.
- Nombres, tipos y restricciones de campos.
- Direcciones de relación (varios a uno, muchos a muchos)
- Requisitos de datos de semillas
- Pasos de verificación
No necesita ver un lenguaje de definición de esquema; simplemente lee su inglés simple y construye la estructura.
Paso 2: la habilidad genera cargas útiles correctas
Aquí es donde brilla la habilidad. El punto final POST /collections de Directus es notoriamente exigente:
¿Sin esquema: {} y meta: {} por campo? → 403 Prohibido (incluso con un token de administrador válido).
¿Nombre de tipo de campo incorrecto? → La colección no aparece silenciosamente.
¿Faltan metaopciones de relación? → Las relaciones no funcionan.
La habilidad conoce el contrato API de Directus y genera cargas útiles con producción correcta automáticamente. Así es como se ve una definición de campo correcta:
{
'campo': 'precio_centavos',
'tipo': 'entero',
'schema': {'is_required': True}, # ← siempre incluye esto
'meta': {'interface': 'numeric'} # ← siempre incluye esto
}Paso 3: llamadas a la API de administrador en orden
Las colecciones se crean en orden de dependencia (padres antes que hijos, sin departamentos circulares). Para cada uno:
POST /collectionscrea la colección con la lista de campos completa- Skill verifica con
GET /collections/{name}que existe - Si ya existe, se elimina y se vuelve a crear (modo idempotente)
Para tablas de unión (M2M), se utilizan el tipo pivote o campos FK explícitos con meta.interface: 'relation'.
Paso 4 - Datos de semillas + Verificación
Una vez que existen todas las colecciones, el agente:
- Inserta registros de muestra usando CRUD estándar (
insert()) - Consultas nuevamente con
get()oget_expanded()para confirmar - Imprime un resumen en tu terminal
Patrones probados en producción que hemos descubierto
Hemos ejecutado este flujo de trabajo en https://hub.aratech.ae con nuestro esquema de blog completo. Aquí están las lecciones:
1. El esquema y el meta son obligatorios
Esto no se puede enfatizar lo suficiente. Directus 10+ devuelve 403 cuando falta alguno de ellos, pero el mensaje de error es engañoso ("No tienes permiso") cuando el problema real es la estructura de tu carga útil.
Comprueba siempre:
- La carga útil de la colección tiene
schema: {}ymeta: {}en el nivel superior - Cada campo tiene tanto "esquema" como "meta"
- Los valores de campo
typeson tipos Directus válidos (integer, noint;string, novarchar)
2. Utilice get_expanded() para relaciones
Directus GraphQL requiere conjuntos de selección anidados para campos de relación. Si lo intentas:
{ Blog_Posts_Translations { id blog_posts_id } }Aparece un error 400: "El campo "blog_posts_id" del tipo "Blog_Posts" debe tener una selección de subcampos.
El método get_expanded() de la habilidad con notación de puntos maneja esto automáticamente:
cliente.get_expanded(
campos = ['id', 'título', 'blog_posts_id.id', 'blog_posts_id.status'],
colección = 'Blog_Posts_Translations',
límite=50
)
## Devuelve dictados: blog_posts_id = {'id': '5', 'status': 'publicado'}3. Los ID son cadenas en GraphQL, enteros en REST
GraphQL devuelve todos los campos "id" como cadenas. Pero los campos de clave externa (como blog_posts_id) son enteros cuando se usan como filtros o insertan valores.
Cuando creas una publicación y obtienes post_id = "42", debes transmitir:
cliente.insertar({
'blog_posts_id': int(post_id), # NO post_id
'languages_code': 'es',
...
}, colección='Blog_Posts_Translations')De lo contrario, obtendrá un error de validación 400 GraphQL.
4. Las escrituras de la tabla de conexiones revisan la colección de conexiones
Directus expone las relaciones M2M como campos virtuales de solo lectura en el padre. No puedes PATCH el campo etiquetas en Blog_Posts directamente; obtienes 403 Prohibido.
En su lugar, escriba en la tabla de unión:
## INCORRECTO: El PATCH directo en Blog_Posts falla
client.update(post_id, {'etiquetas': [1, 2, 3]}, colección='Blog_Posts') # ❌ 403
## CORRECTO: Insertar en la mesa de unión
para tag_id en [1, 2, 3]:
client.insert({'blog_posts_id': post_id, 'blog_tags_id': tag_id}, colección='Blog_Posts_Tags')5. Las colecciones de traducción necesitan inserciones de dos pasos
Según nuestro diseño de esquema: primero cree el registro principal Blog_Posts, luego cree el registro secundario Blog_Posts_Translations con el FK:
## Paso 1: padre (solo metadatos)
publicación = cliente.insertar({
'estado': 'publicado',
'autor': autor_id,
'categoría': categoría_id,
'tiempo_lectura_minutos': 8
}, colección='Blog_Posts')
post_id = publicación['id']
## Paso 2: traducción (contenido real)
cliente.insertar({
'blog_posts_id': post_id,
'languages_code': 'es',
'título': 'Mi publicación',
'babosa': 'mi-publicación',
'content': '#Hola mundo...',
...
}, colección='Blog_Posts_Translations')6. El error de truncamiento POST y la solución PATCH
Descubrimos que los POST de campos de "contenido" grandes (>~1500 caracteres) se truncan silenciosamente mediante una capa de puerta de enlace/proxy. La respuesta REST devuelve éxito, pero solo se almacena el primer ~1,5 KB de su descuento.
Mitigación: cree el registro de traducción con datos iniciales, luego PATCH inmediatamente el campo "contenido":
## Paso A: Crear (riesgo de truncamiento)
trans = client.insert({... campos_iniciales..., 'contenido': 'marcador de posición corto' }, colección='Blog_Posts_Translations')
trans_id = trans['id']
## Paso B: PATCH contenido completo (evita el truncamiento)
client.update(trans_id, {'content': full_markdown_body}, colección='Blog_Posts_Translations')7. Verifique el acceso de escritura antes de publicar
Antes de intentar una escritura crítica (publicación, actualización masiva), realice una prueba ligera:
## Enviar un registro de prueba mínimo
prueba = {'estado': 'borrador', 'autor': 1, 'categoría': 33}
test_record = client.insert (prueba, colección = 'Blog_Posts')
test_id = test_record['id']
## Si tiene éxito, elimínelo
client.delete(test_id, hard=True, colección='Blog_Posts')
print('✅ Acceso de escritura confirmado')Si esto falla con 403/404, su token no tiene permisos de escritura; no tiene sentido intentar realizar la operación completa.
Manejo de errores: qué sale mal y cómo solucionarlo
Aquí está la tabla de errores de producción:
La habilidad ya maneja la mayoría de estos internamente (v1.4.1+), pero es bueno conocer los patrones.
Patrones avanzados: filtros, relaciones y campos de enumeración
Filtrado con operadores
La habilidad es compatible con todos los operadores de filtros Directus:
## Igual
resultados = cliente.get(
campos = ['id', 'título', 'estado'],
filtros={'status': {'_eq': 'publicado'}},
colección = 'Blog_Posts',
límite=10
)
## No igual
borradores = client.get(filters={'status': {'_neq': 'published'}}, collection='Blog_Posts')
## Contiene (no distingue entre mayúsculas y minúsculas)
buscar = cliente.get(
filtros = {'título': {'_icontains': 'seguridad'}},
colección = 'Blog_Posts'
)
## En la lista
resultados = cliente.get(
filtros = {'categoría': {'_in': [33, 37, 42]}},
colección = 'Blog_Posts'
)
## Mayor que
caro = cliente.get(
filtros={'price_cents': {'_gt': 10000}},
colección='Productos'
)
## Verificación nula
no contactado = cliente.get(
filtros = {'last_contacted_at': {'_null': Verdadero}},
colección='Contactos'
)
## Combinado (Y juntos)
resultados = cliente.get(
filtros={
'estado': {'_eq': 'publicado'},
'categoría': {'_in': [33, 37]},
'published_at': {'_not_null': Verdadero}
},
colección = 'Blog_Posts'
)Expansión de relaciones multinivel
Con la notación de puntos, puedes recuperar relaciones anidadas en una sola llamada:
## Obtener publicaciones con autor, categoría y etiquetas
registros = client.get_expanded(
campos=[
'id', 'título', 'estado',
'autor.id', 'autor.nombre', 'autor.correo electrónico', # relación M2O
'categoría.id', 'categoría.nombre', # relación M2O
'tags.id', 'tags.name', 'tags.slug' # M2M mediante unión
],
colección = 'Blog_Posts',
límite=20
)
## Resultado: cada registro tiene dictados anidados como {'autor': {'id': 1, 'nombre': 'Alex'}, 'etiquetas': [{'id': 5, 'nombre': 'AI'}, ...]}Campos JSON para datos flexibles
Utilice el tipo json para datos estructurados arbitrarios:
cliente.insertar({
'dirección_envío': {
'calle': '123 Tech Blvd',
'ciudad': 'Dubái',
'país': 'EAU',
'código_postal': '00000'
},
'metadatos': {
'utm_source': 'boletín',
'campaign_id': 'Q2-2026-ai-launch'
}
}, colección='Pedidos')El JSON se almacena de forma nativa y se puede consultar con filtros en claves anidadas.
La API de administración: cuando necesita tocar el esquema mediante programación
La habilidad expone create_collection() y create_field() a través de Directus admin REST API (/collections y /fields/{collection}), no GraphQL.
Se requiere token de administrador. Su DIRECTUS_API_TOKEN es para CRUD; DIRECTUS_ADMIN_TOKEN es para operaciones de esquema.
cliente = DirectusCliente(
url=os.getenv('DIRECTUS_URL'),
token=os.getenv('DIRECTUS_API_TOKEN'), # para CRUD
admin_token=os.getenv('DIRECTUS_ADMIN_TOKEN') # para esquema
)
## Crea una nueva colección
client.create_collection('TEST_Items', campos=[
{'field': 'id', 'type': 'integer', 'schema': {'is_primary_key': True, 'has_auto_increment': True}, 'meta': {'hidden': True, 'interface': 'numeric', 'readonly': True}},
{'campo': 'nombre', 'tipo': 'cadena', 'esquema': {}, 'meta': {'interfaz': 'entrada'}},
{'campo': 'valor', 'tipo': 'entero', 'esquema': {}, 'meta': {'interfaz': 'numérico'}}
])
## Agregar un campo a la colección existente (úselo con precaución, puede dañar el esquema GraphQL)
client.create_field('TEST_Items', 'descripción', 'texto', meta={'interfaz': 'textarea'})
## Listar todas las colecciones
colecciones = client.get_collections()
print(colecciones) # ['Blog_Posts', 'Blog_Authors', ...]
## Obtener esquema de colección
esquema = client.get_collection_schema('Blog_Posts')
print(esquema['campos'].keys()) # dict_keys(['id', 'estado', 'autor', ...])Advertencia: Agregar campos incrementalmente a través de create_field() puede dañar el esquema GraphQL para esa colección, lo que hace que todas las consultas GraphQL posteriores fallen con 400. El patrón más seguro: defina cada campo por adelantado en create_collection().
Poniéndolo todo junto: su primera sesión de Vibe-Code
Así es como se ve un flujo de trabajo completo:
## 1. Inicie su terminal (con tokens Directus en env)
$ exportar DIRECTUS_URL="https://hub.aratech.ae"
$ export DIRECTUS_API_TOKEN="sZk-st..." # token de usuario
$ export DIRECTUS_ADMIN_TOKEN="anuncio-..." # token de administrador
## 2. Inicie el agente Hermes
$hermes
## 3. Da el mensaje (copia y pega desde arriba)
Tú: Constrúyeme un backend de blog con Directus. Crea estas colecciones:...
## 4. El agente piensa, genera script y ejecuta
🤖 Agente: Creando Blog_Autores... ✅
Creando Blog_Categorías... ✅
Creando Blog_Tags... ✅
Creando publicaciones_de_blog... ✅
Creando Blog_Publicaciones_Traducciones... ✅
Creando etiquetas_publicaciones_blog... ✅
Sembrando 2 publicaciones de muestra... ✅
Verificación:
- Blog_Posts: 2 registros
- Blog_Posts_Translations: 2 registros (solo en inglés)
- Blog_Autores: 1 registro
- Blog_Categories: 1 registro
- Blog_Tags: 0 registros
✅ ¡Backend andamiado exitosamente! Su panel de administración de Directus en https://hub.aratech.ae/admin ahora muestra todas las colecciones completas.
## 5. Ya terminaste. Verifique la interfaz de usuario del administrador o consulte:
$ python3 -c "de directus.client importar DirectusClient; c = DirectusClient(); print(c.get(colección='Blog_Posts', límite=3))"Eso es todo. Desde una instancia de Directus en blanco hasta un backend de blog completo con relaciones y datos de muestra en menos de un minuto.
Qué permite esto: creación rápida de prototipos a escala
No se trata sólo de ahorrar tiempo en la creación de esquemas (aunque eso es enorme). Se trata de reducir el ciclo de retroalimentación entre la idea y el prototipo funcional:
- Diseño de esquema de una semana → 5 minutos para describir lo que deseas
- Archivos de migración manual → Un script generado y ejecutado
- Redacción de especificaciones de API → Directus genera automáticamente puntos finales REST y GraphQL
- Configuración del panel de administración → La interfaz de usuario de administración de Directus está lista inmediatamente con sus colecciones, campos y permisos
Ahora puedes activar un backend específico del proyecto para:
- Herramientas internas (inventario, emisión de tickets, seguimiento de activos)
- Portales de clientes (historial de pedidos, tickets de soporte)
- Modelos de datos SaaS (suscripciones, inquilinos, registros de uso)
- MVP para el trabajo del cliente (describa el dominio, obtenga un backend instantáneo respaldado por CMS)
Y como Directus es solo su base de datos más una capa API inteligente, usted es dueño de los datos. Sin dependencia del proveedor. Exporte el SQL en cualquier momento.
Limitaciones y trampas
No todos los patrones de backend se ajustan a Directus
Directus es la primera plataforma CMS. Destaca en:
- Modelos centrados en contenido (blogs, noticias, documentos, catálogos)
- Datos relacionales estructurados (usuarios, pedidos, productos)
- Aplicaciones con muchos medios (administración de archivos integrada)
No es ideal para:
- Sistemas transaccionales de alta frecuencia (banca, licitaciones en tiempo real)
- Arquitecturas complejas de abastecimiento de eventos o CQRS
- Sistemas que requieren funciones SQL personalizadas o activadores
- Websockets en tiempo real (aunque puedes usar sus ganchos y webhooks)
Aún necesitas pensar en el modelado de datos
La codificación Vibe no reemplaza la comprensión de su dominio. Basura entra, basura sale:
- Campos mal nombrados → API confusa
- Índices faltantes → consultas lentas a escala
- Estructura plana para anidamiento profundo → lecturas ineficientes
La habilidad se desarrolla según tus especificaciones. Las buenas especificaciones provienen de un buen modelado.
Los permisos aún necesitan configuración
La habilidad crea colecciones y campos como administrador, pero aún necesita configurar permisos Directus (roles y políticas) para exponer los datos correctos a los usuarios frontend. La habilidad aún no toca los permisos.
El contenido grande requiere parches
Si publica artículos con rebajas de más de 1500 caracteres, utilice siempre el patrón POST-luego-PATCH para evitar el truncamiento. La habilidad aún no se parchea automáticamente; debe manejarla en su secuencia de comandos de inicialización.
Conclusión: el backend es ahora una conversación
Construir un backend solía significar escribir migraciones de SQL, configurar un ORM, crear rutas de controlador y conectar un administrador. Días o semanas de trabajo.
En 2026, con Directus y la habilidad nikola66, es:
- Describe tu esquema en inglés sencillo.
- Deje que el agente genere y ejecute Python.
- Tu backend está activo
Hemos utilizado este patrón para poner en marcha tres backends de proyectos solo este mes. Lo que solía ser un obstáculo - "primero necesitamos construir la capa de datos" - ahora no es un problema. El cuello de botella se movió hacia arriba: ahora dedicamos nuestro tiempo a la lógica del producto y la UX, no a la plomería.
La era de la codificación de vibraciones está aquí. Su backend estará listo cuando usted lo esté.
Próximos pasos
- Instalar la habilidad:
npx skills add nikola66/directus-skill - Conectar a Directus: Establezca
DIRECTUS_URL,DIRECTUS_API_TOKEN,DIRECTUS_ADMIN_TOKEN - Pruebe el ejemplo de blog anterior (crea un esquema multilingüe totalmente consultable)
- Extiéndalo: agregue colecciones para comentarios, me gusta, suscripciones y registros de webhooks
- Conectar permisos en Directus Admin → Configuración → Roles y políticas
¿Tienes preguntas? Envíenos un mensaje a Telegram @Hermdroid o abra un problema en el repositorio de habilidades. Feliz edificio.
Tiempo de lectura: ~9 minutos Categoría: Tutorial Etiquetas: Directus, desarrollo backend, agentes de IA, codificación Vibe, CMS, GraphQL, Python, esquema de base de datos, API, sin código