Documentación
Referencia de Velk
Lo esencial para construir y mantener un sitio Velk. La skill oficial carga esta misma referencia, completa, dentro de tu IA.
Empezando
Requisitos
- PHP 8.2 o superior
- MySQL 5.7+ o MariaDB 10.2+ (Velk usa columnas JSON nativas)
- Apache con .htaccess (incluido) — en nginx, escribe la regla de rewrite a mano
Instalación con IA (recomendada)
En el flujo de vibe coding no descargas nada: la skill oficial se encarga. Instálala en Claude Code y pide la instalación — la IA descarga la release oficial, verifica el checksum, configura el .env y ejecuta el instalador. El paso a paso está en Cómo usar.
Instala la skill elleven-digital/velk-skill en ~/.claude/skills/velk y recarga las skills.
Instalación manual (sin IA)
Para quien prefiere programar directo: Velk se distribuye como un tarball versionado — no es un clon de repositorio. Descarga la release, extráela en la carpeta del proyecto, rellena el .env a partir del .env.example (base de datos y usuario admin inicial) y ejecuta el instalador:
$ ./bin/velk install
crea la base de datos si falta · corre migraciones · crea el admin · copia .htaccess y robots.txt
$ ./bin/velk serve
Velk corriendo en http://localhost:8080
Variables principales del .env: DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD, INITIAL_USER_EMAIL, INITIAL_USER_PASSWORD, INITIAL_USER_NAME y APP_URL (opcional en local — usada por el sitemap y por absolute_url()). SMTP es opcional: sin SMTP_HOST/USER/PASS/FROM, los formularios guardan pero no envían email.
Estructura de carpetas
core/ engine — nunca editar
bin/velk CLI
migrations/ engine — aplicadas en la instalación/actualización
theme/ TU sitio: site.json, plantillas, assets
storage/ uploads y archivos generados
.env credenciales
.velk-version versión instalada (usada por el upgrade)
site.json
theme/site.json es la fuente de la verdad de la estructura: el panel administrativo se genera de él. Claves de nivel superior:
| Clave | Lo que declara |
|---|---|
| site | name y language. La URL de producción no va aquí — es APP_URL en el .env, manteniendo el tema portátil. |
| field_groups | Bundles de campos reutilizables, referenciados como {"group": "contact"}. |
| image_sizes | Tamaños con nombre (width, height, crop) para image_url(). |
| pages | Páginas estructurales por clave (ej.: home): label, template, URL opcional y campos. |
| page_templates | Plantillas para el flujo "+ Crear página" del admin — la página vive en la base de datos, con URL /{slug}. |
| item_types | Tipos de contenido: label, slug, template, archive_template, taxonomies, plantillas alternativas y campos. |
| taxonomies | Taxonomías: label, slug, hierarchical, template y has_page (false quita la URL pública del término). |
| options | Páginas de opciones globales — solo campos, sin slug ni listado. |
| forms | Formularios públicos (campos, asunto del email, mensaje de éxito). |
| members | Área de miembros del sitio (tipos, campos, rutas). |
| i18n | Multi-idioma: enabled e idioma default. |
Las plantillas se vinculan por nombre de archivo, nunca por slug — renombra slugs a voluntad sin romper los bindings. Tras cualquier edición, ejecuta ./bin/velk schema:validate.
Tipos de campo
Comunes a cualquier campo: name (obligatorio), label, help, required, placeholder. field('name') devuelve el valor crudo — escalar en los simples, array en gallery/repeater/flexible.
| Tipo | Notas |
|---|---|
| text | Input de una línea. |
| textarea | Texto largo sin formato. |
| richtext | WYSIWYG (TipTap); guarda HTML. Imprímelo sin escapar, comentado /* trusted */. |
| image | Un medio, almacenado como id entero. Resuélvelo con image_url(). |
| gallery | Lista ordenada de ids de medios — pasa cada uno por image_url(). |
| select | Desplegable; options como {value: label}. |
| boolean | Checkbox. |
| number | Input numérico. |
| email · url · date | Variantes HTML5 del input. |
| repeater | Grupo de subcampos repetible; declara fields en el campo. |
| flexible | Repeater donde cada fila elige un layout con nombre; la fila guardada lleva _layout. |
| link | Compuesto {title, url, new_tab}; imprime con link_tag(). Acepta mailto:, anclas y rutas relativas. |
| item | Referencia a otro item (combobox solo con publicados). Declara item_type; resuélvelo con item(field('x')). |
| group | Solo organización: caja plegable en el admin. El almacenamiento sigue plano. |
| tabs | Solo organización: pestañas en el editor. Dos tabs no pueden repetir nombre de campo. |
Plantillas
Orden de resolución:
- Página estructural: template → fallback page.php
- Página creada en el admin: file de la plantilla elegida → page.php
- Item: la plantilla propia del item (si hay) → la plantilla del tipo → single.php
- Archivo de items: archive_template → archive.php
- Archivo de taxonomía: la plantilla de la taxonomía → taxonomy.php
<?php with_context($item, function () {
get_header(); ?>
<article class="post">
<h1><?= e(the_title()) ?></h1>
<?= field('content') /* trusted */ ?>
</article>
<?php get_footer(); }); ?>
with_context() permite que field(), the_title() y compañía funcionen sin pasar el registro alrededor. Cada tipo de plantilla recibe una variable de contexto:
| Plantilla | Variable | Contenido |
|---|---|---|
| page-*.php | $page | ->record es la página |
| single-*.php | $item | ->record es el item |
| archive-*.php | $archive | ->records es la lista de items |
| taxonomy-*.php | $term | ->record es el término, ->records los items |
Helpers
Todos globales en las plantillas (definidos en core/helpers.php).
Salida y contexto
| Helper | Hace |
|---|---|
| e($value) | Escapa para HTML. Úsalo en toda salida — excepto richtext de confianza. |
| dd(...$v) | Debug: vuelca y termina. |
| field($name, $default) | Campo del contexto actual (idioma ya resuelto). |
| the_title() · the_slug() · the_url() | Básicos del registro actual. |
| with_context($ctx, $fn) | Ejecuta el bloque con ese registro como contexto. |
Contenido
| Helper | Hace |
|---|---|
| items($type, $args) | Lista items — limit, offset, status (por defecto published), term, order. |
| item($id) | Un item (o null) — resuelve campos item. |
| terms($taxonomy) | Términos de una taxonomía. |
| the_terms($taxonomy) | Términos del item actual. |
URLs y medios
| Helper | Hace |
|---|---|
| url($path) | Enlace de navegación con el base path de la instalación (y el idioma activo). |
| absolute_url($path) | URL completa (scheme + host) — canonical, og:url, sitemap. |
| asset($path) · admin_url() · base_path() | Assets del tema, URL del admin, base de la instalación. |
| image_url($value, $size) | Resuelve id/URL/array en una URL; '' cuando está vacío — revisa antes del <img>. |
| image_alt($value, $fallback) | Texto alternativo (consciente del idioma). |
| media($id) | El modelo del medio (width/height/mime). |
| link_tag($link, $text, $attrs) | <a> seguro para un campo link; una nueva pestaña recibe rel="noopener". |
Layout y varios
| Helper | Hace |
|---|---|
| get_header() · get_footer() | Incluyen theme/partials/header.php y footer.php. |
| partial($name, $data) | Incluye un parcial con datos. |
| option($path, $default) · options($key) | Opciones globales por dot path (option('contact.phone')). |
| slugify($string) · config($key) · site($key) | Utilidades. |
URLs y rutas
- / → página de inicio
- /{page-key} → demás páginas estructurales (o la URL explícita declarada)
- /{type-slug} → archivo · /{type-slug}/{item-slug} → item
- /{taxonomy-slug}/{term-slug} → archivo del término
- /admin → panel · /uploads/... → medios · /theme/... → assets estáticos del tema (sin PHP)
El base path se detecta solo desde SCRIPT_NAME, así que el sitio funciona instalado en la raíz o en un subdirectorio (site.com/velk/) sin configurar nada — siempre que el tema use siempre los helpers de URL. Si un host exótico falla la detección: 'base_path' => '/velk' en config/env.php.
Medios
Biblioteca en /admin/media: subida (jpeg, png, gif, webp, svg, pdf — hasta 25 MB), texto alternativo, copiar URL y borrado que limpia los tamaños generados. Los nombres de archivo se slugifican; las colisiones reciben sufijo -1, -2…
storage/uploads/{file}.{ext} originales
storage/uploads/thumb/{file}.{ext} generado bajo demanda (GD)
storage/uploads/hero/{file}.{ext} generado bajo demanda (GD)
Los tamaños vienen de image_sizes en el site.json — crop: true recorta exacto, false encaja proporcional. En el tema: image_url(field('photo'), 'thumb').
Opciones globales
"options": {
"contact": {
"label": "Contact",
"fields": [
{ "name": "phone", "type": "text", "label": "Phone" }
]
}
}
Cada entrada se convierte en un enlace en la barra lateral del admin y en un formulario solo de campos. Léelo con option('contact.phone') — el dot path entra en grupos y filas de repeater (option('contact.social.0.url')). Toda una página sale con options('contact'). Almacenado como una fila JSON en settings, con caché en proceso: 50 llamadas, una query.
Formularios
"forms": {
"contact": {
"label": "Contact",
"subject": "New contact: {{name}}",
"success_message": "Message sent.",
"fields": [
{ "name": "name", "type": "text", "label": "Name", "required": true }
]
}
}
Tipos aceptados: text, email, tel, url, number, textarea, checkbox, hidden, select (con required y maxlength). El asunto acepta placeholders {{field}} rellenados con el envío.
El tema escribe el HTML del formulario:
- form_url('contact') en el action (POST)
- csrf_field() + form_honeypot() dentro del form
- form_status('contact') para leer el flash — status, mensaje, valores y errores
Ciclo: valida CSRF + honeypot + campos, guarda en form_submissions, envía el email a los destinatarios definidos en /admin/forms/contact y redirige de vuelta. Un email fallido no pierde el envío.
Miembros
Cuentas del sitio público — tablas, autenticación y sesiones separadas de los usuarios del admin. Sin roles predefinidos: cada sitio declara sus tipos y el tema decide a qué accede cada uno.
"members": {
"enabled": true,
"types": {
"client": { "label": "Client",
"fields": [ { "name": "company", "type": "text" } ] }
},
"routes": { "login": "/login" }
}
| Helper | Hace |
|---|---|
| member_enabled() · member_check() | ¿Función activa? ¿Alguien con sesión iniciada? |
| member() · member_field('x') · member_is('type') | Miembro actual, su campo, comprobación de tipo. |
| member_require() · member_require_type('type') | Bloquea la página; redirige a quien no puede. |
| member_route('login') · member_status() | Ruta resuelta y último flash de auth. |
El registro público viene desactivado (public_registration: false) — por defecto solo el admin crea cuentas. El tema construye los formularios de login/registro/reset contra los endpoints POST del engine, siempre con csrf_field().
Multi-idioma
Tres capas independientes, todas nativas:
- Cadenas fijas del tema — envuelve en t('Leer más'); catálogo generado por i18n:scan y traducido en /admin/translations.
- URLs estructurales — slugs de páginas, tipos y taxonomías por idioma: /blog/post-x se convierte en /en/news/post-x.
- Contenido por registro — título, slug y campos con columnas *_translations y pestañas por idioma en el propio editor.
"i18n": { "enabled": true, "default": "en" }
Los demás idiomas se registran en /admin/languages, cada uno con prefijo de URL. En las plantillas nada cambia: field(), the_title() y option() ya resuelven el idioma activo con fallback al predeterminado — escribe la plantilla una vez.
| Helper | Hace |
|---|---|
| lang() · lang_is('en') · available_languages() | Idioma actual y lista de idiomas. |
| url('/about', 'en') · lang_url('en') | URL en otro idioma; la página actual en otro idioma. |
| lang_switcher() | Datos listos para montar el selector de idiomas. |
| the_html_lang() · the_hreflangs() · the_canonical() | El atributo del <html>, enlaces alternate y canonical. |
Las URLs de idioma no predeterminado son estrictas — sin slug traducido, sin URL en ese idioma (nada de contenido duplicado para Google). El sitemap emite los hreflang solo cuando la traducción existe de verdad.
CLI
./bin/velk <comando>, desde la raíz del proyecto. Casi todo lo que hace el panel, lo hace la CLI — es lo que permite que la IA opere el sitio.
Setup y esquema
| Comando | Hace |
|---|---|
| install | Instalación completa: base de datos, migraciones, admin, archivos del proyecto. |
| migrate · migrate:status | Aplica / lista las migraciones pendientes. |
| schema:validate | Valida el site.json — ejecútalo tras cada edición. |
| page:sync | Crea/actualiza las filas de páginas declaradas en el esquema. |
| serve [port] | Servidor de desarrollo (por defecto :8080). |
| user:create | Nuevo usuario admin. |
| files:init | Copia .htaccess y robots.txt desde los .example. |
| i18n:scan | Recorre el tema en busca de t() y registra las cadenas. |
Contenido
| Comando | Hace |
|---|---|
| item:types · item:schema <type> | Lista tipos; muestra los campos de un tipo (lee antes de escribir). |
| item:list · item:get | Lista (con filtros) y lee un item como JSON. |
| item:create --json= · item:update --json= | Crea y actualiza (merge) por JSON. |
| item:publish · item:unpublish · item:delete --confirm | Cambia el estado; elimina (requiere --confirm). |
| page:list · page:schema · page:get · page:update | Lo mismo, para páginas. |
Flags útiles
- --format=json — salida legible por máquina
- --dry-run — valida sin guardar
- --json-stdin — payload por stdin, sin infierno de comillas en el shell
Deploy y upgrades
Deploy
- Hosting compartido sin SSH: sube los archivos por FTP/panel — el auto-migrate completa la instalación en la primera petición.
- Con SSH (VPS, cloud, cPanel con terminal): la skill oficial cubre el flujo completo — archivos, base de datos, storage y robots.txt con la URL de producción.
- Subdirectorio: funciona sin configuración, porque los helpers de URL prefijan el base path solos.
En producción, rellena APP_URL en el .env — el sitemap y absolute_url() dependen de él.
Upgrades
El engine es una dependencia versionada, distribuida como tarball con checksum. El upgrade lee la versión en .velk-version, descarga la nueva, verifica el sha256 y reemplaza solo las rutas del engine — theme/, storage/, .env, .htaccess y robots.txt quedan como están. Pídele a la IA: "actualiza Velk en este proyecto".
La referencia completa vive en la skill
Instala la skill oficial y tu IA pasa a consultar toda esta documentación — esquema, helpers, CLI y flujos de deploy — sin salir del chat.