En este manual analizamos algunos conceptos básicos para el desarrollo de JuncoCMS.
Para conocer más sobre el desarrollo de JuncoCMS, puede continuar leyendo los manuales:
A continuación se listarán una serie de conceptos que son imprescindibles para entender el CMS.
Una aplicación es un programa informático, diseñado como una herramienta para realizar tareas o funciones específicas.
La aplicación esta compuesta de una o varias extensiones, que facilitan la lógica y optimizan la resolución de los problemas a los que se enfrenta una aplicación.
Una extensión es una porción de código, que puede tener una gran diversidad de funciones, pero una única idea generadora, es decir, propósitos muy específicos.
Dentro de una extensión puede haber librerías, administración, plugins o cualquier otro tipo de componentes, y todos deben servir para llevar a cabo un único tipo de servicio. Por ejemplo, la extención Invoices tiene una librería para crear recibos, y también una administración para realizar el seguimiento de esos recibos.
Es una forma muy general de nombrar todo lo necesario para generar una página web. Bajo el concepto de componente podemos agrupar cosas tan diferentes, como pueden ser: una librería php o javascrip, un plugin o una carpeta donde guardar archivos multimedia.
Un archivo comprimido que contiene una o más extensiones, compiladas y listas para distribuir o instalar.
Acción de reunir en un mismo espacio (una carpeta) todo lo referido a una o varias extensiones. En la compilación habrá archivos, estructuras de la base de datos y datos de varios tipos, como por ejemplo: configuraciones, menús, etc . El código se compila para su distribución.
Acción de subir un paquete a la Tienda en línea.
Se trata de la disponibilidad, en la Tienda en línea, de una nueva versión de una extensión, y/o la acción de descargar e instalar esa nueva versión.
En la raíz, la estructura de carpetas es la siguiente:

Contiene los archivos propios de cada una de las aplicaciones.
Contiene los archivos javascript y css compilados por la extensión Assets.
Contiene todo tipo de librerías de uso común entre las aplicaciones.
Contiene los recursos estáticos administrados por las aplicaciones. Pueden ser: imágenes, videos, documentos, etc.
Contiene archivos que almacenan los datos que personalizan el sitio. Algunos de estos datos pueden ser: configuraciones, traducciones, logs, temas, caché, temporales, etc.
Contiene las librerías cargadas con composer.
Archivo con las configuraciones para inicializar el sistema.
Un modelo de archivo de configuraciones del servidor, sirve, por ejemplo, para activar el módulo mod_rewrite y reescribir las url. Para ponerlo en funcionamiento, debe renombrarlo colocándole un punto al comienzo.
Archivo único de acceso a los recursos dinámicos.
La licencia para el uso de este software.
Contiene todos los archivos propios de una aplicación, vale decir, no compartidos con otras aplicaciones. Estos archivos serán reemplazados en cada actualización.
Dentro de la carpeta de app/ cada extensión tendrá su propia carpeta. Esta es una imagen de app/ al momento de instalar el CMS:

Ahora bien, en cada carpeta de app/ se encuentran los archivos que forman parte de la aplicación. Todos los archivos estarán ordenados con patrones de diseños predefinidos.
A continuación, a modo de ejemplo, mostramos el contenido de la carpeta app/settings/:

En general, las carpetas describen el contenido de las mismas y sólo los controladores se encuentran en la raíz.
Los modelos se encuentran en la carpeta app/{{extension}}/models/. Un ejemplo podría ser:

El archivo php y la clase en su interior deberá tener un nombre con la siguiente estructura «{{Component}}{{Subcomponent}}Model».
Muchas veces, por rendimiento, necesidad o mayor claridad, es útil organizar los modelos. Para ello, es posible utilizar subcarpetas. Dentro de las subcarpetas, el nombre del modelo será «{{Subfolder}}{{Component}}{{Subcomponent}}Model».
Para que el controlador incluya las vistas de modo automático, es necesario tener en cuenta algunas convenciones.
El primer nivel de carpetas está dado por el punto de acceso en que el controlador se ejecuta. Luego, se debe tener en cuenta si es un componente o un subcomponente.
Si la vista pertenece a un componente, ya no es necesario seguir agregando carpetas. El nombre de la vista será el de la tarea que ejecutará el controlador en formato PascalCase.
La ruta quedará definida como «app/{{extension}}/views/{{access_point}}/{{Task}}.php».
Si la vista pertenece a un subcomponente se debe crear otra carpeta con el nombre del mismo. El nombre de la vista será, como en el caso anterior, el de la tarea que ejecutará el controlador en formato PascalCase.
La ruta quedará quedará definida como «app/{{extension}}/views/{{access_point}}/{{subcomponent}}/{{Task}}.php».
Los controladores quedarán dispuestos en la raíz de la carpeta. Esto permite tener una idea rápida de los componentes disponibles en una aplicación.
El nombre del archivo tendrá el formato «{{Accesspoint}}{{Component}}{{Subcomponent}}Controller».
Existe una posibilidad de definir url por fuera de las convenciones del CMS. Para ello, se debe crear una carpeta llamada routes/ y colocar allí los archivos donde se definirán las nuevas rutas.
Los archivos deberán ser nombrados como «{{Accesspoint}}{{Component}}Routes.php».
Contiene todo tipo de librerías de uso común entre las aplicaciones. Actualmente, estas librerías son las siguientes:

Contiene las librerías del lado del servidor, es decir, las librerías PHP.
Contiene funciones encargadas de modificar el flujo de salida. Por lo general, son utilizadas para que la salida de una extensión sea enriquecida por el aporte de otras extensiones.
Contiene las librerías y recursos del lado del cliente, es decir, todo lo que no sea PHP, por ejemplo: css, javascript, imágenes, tipografías, pre-procesadores de hojas de estilos, etc.
Contiene clases que generan dinámicamente una porción de html, como ser: un formulario, una lista o un paginador.
Contiene las librerías del lado del servidor, es decir, las librerías PHP.
Las librerías están agrupadas por la extensión a las que pertenecen. Por lo tanto, si observamos dentro de la carpeta cms/libraries/, podemos ver algo similar a:

En cada una de esas carpetas, las librerías deberán ser nombradas comenzando con el nombre de la extensión, por ejemplo «cms/libraries/settings/Settings.php».
Contiene funciones encargadas de modificar el flujo de salida. Por lo general, son utilizadas para que la salida de una extensión sea enriquecida por el aporte de otras extensiones.
La organización de las carpetas tiene tres niveles, esto es así para lograr generar código intercambiable. Los niveles corresponden a:
La ruta completa a un plugin estará dada por:
cms/plugins/{{extension_alias}}/{{plugin_name}}/{{alter_plugin}}/{{plugin_name}}.{{plugin_hook}}.php
Como podemos observar, el nombre del archivo que contiene el plugin también tiene su convención.
Dentro de un plugin, es posible que debamos ejecutar varias tareas, para ello declaramos varios archivos con un hook diferente para cada uno de ellos. Por ejemplo, si definimos un editor bbcode para un formulario, debemos considerar el hook para cargar el editor en dicho formulario y un hook para procesar el contenido del editor.
Contiene las librerías y recursos del lado del cliente, es decir, todo lo que no sea PHP, por ejemplo: css, javascript, imágenes, tipografías, pre-procesadores de hojas de estilos, etc.
Las librerías están agrupadas por la extensión a las que pertenecen. Por lo tanto, si observamos dentro de la carpeta cms/scripts/, podemos encontrar algo similar a:

Dentro de cada extensión, no hay convenciones, aunque es una buena práctica separar el código según su tipo: css/, font/, js/, sass/, etc.
Contiene clases que generan dinámicamente una porción de html, como ser: un formulario, una lista o un paginador. Su estructura de tres niveles, similar a la de plugins, tiene como fin generar fragmentos intercambiables entre sí.
Los tres niveles corresponden a:
La ruta completa a un fragmento estará dada por:
cms/snippets/{{extension_alias}}/{{snippet_name}}/{{alter_snippet}}/snippet.php
La alternativa por defecto para los fragmentos será master.
Contiene los recursos estáticos administrados por las aplicaciones. Pueden ser: imágenes, videos, documentos, etc. El contenido está agrupado por la extensión a las que pertenecen.
Para la compilación, puede ser útil utilizar el archivo .gitignore. Las directivas que permite el compilador son: *, **, !. Para más información gitignore.
Contiene archivos que almacenan los datos que personalizan el sitio. Algunos de estos datos pueden ser: configuraciones, traducciones, logs, temas, caché, temporales, etc.
En su interior, las carpetas dependen de las extensiones, no hay convenciones, aunque sí existen algunas carpetas que utiliza el sistema, como ser:
Utilizada por la extensión homónima para almacenar los datos de compilación y los temas.
Utilizada por la extensión homónima para almacenar el caché.
Utilizada por la extensión language para almacenar las traducciones.
Puesta a disposición por el sistema para que sea utilizada por aquellas extensiones que deseen almacenar registros.
Utilizada por la extensión homónima para almacenar las configuraciones.
Utilizada por la extensión settings para crear, a partir de textos, un código PHP que contenga las funciones de gettext. De este modo, los textos se hacen "visibles" para la herramientas de traducción, como por ejemplo poedit.
Carpeta temporal utilizada por la extensión extensions para la descarga e instalación de actualizaciones.
El CMS está pensado para ser rápido, incluso a la hora de desarrollar nuestras propias extensiones.
Desde su instalación, contiene herramientas que permiten crear y distribuir extensiones de manera sencilla.
A continuación, se listan algunas de estas herramientas, y se explica su funcionamiento.
El Modo desarrollador es una configuración del sistema, que permite habilitar distintas herramientas que facilitan el trabajo de los desarrolladores.
En nuestro caso, permitirá la administración completa de los desarrolladores y de las extensiones.
Para activar el modo desarrollador, en la administración debemos dirigirnos a Configuraciones > Configuraciones > Sistema > General.

Una vez activo el modo desarrollador, si nos dirigimos a Configuraciones > Extensiones, allí podemos acceder a la administración de los desarrolladores. Para ello, presione el botón:

El formulario para crear un desarrollador requiere los siguientes datos:
El nombre del desarrollador.
Con este nombre, las distribuciones identificarán las extensiones que le pertenecen. Si en algún momento es modificado, se perderá todo vínculo con las extensiones distribuidas.
Un sitio web de referencia. Este valor puede ser modificado sin problemas.
El punto de acceso a las distribuciones, tanto para consultar la lista de aplicaciones, como para descargarlas o actualizarlas.
También, es posible utilizar la direccón para distribuir actualizaciones.
Se utiliza para distribuir extensiones. El token se gestiona en la tienda en línea.
Estos valores se utilizan, de forma predeterminada, al crear nuevas extensiones.
Los créditos de las extensiones desarrolladas.
La licencia legal con que serán distribuidas las extensiones.
En Administración > Configuraciones > Extensiones, tenemos la administración de las extensiones.
Allí podrás ver todas las extensiones instaladas y, activado el modo desarrollador, crear y administrar nuevas extensiones.
El formulario para crear una extensión tiene cuatro bloques:
El nombre con el que será identificada internamente la extensión. Todas las convenciones del CMS utilizan este identificador.
El nombre de la extensión. Su uso es meramente descriptivo.
Un texto descriptivo, un poco más extenso que el nombre.
El desarrollador bajo el cual será distribuida la extensión.
Los créditos del desarrollo de la aplicación. Este valor puede coincidir con el desarrollador, pero también se pude ampliar a librerías de terceros.
Las licencias de uso del código.
Cuando se dejan vacíos estos valores, serán completados con los predeterminados para el desarrollador seleccionado.
Las extensiones y versiones que requiere para su instalación en otros sitios. El formato deberá ser extension1:version;extension2:version2....
Si solo se declara un valor de versión, se entiende que se está refiriendo a system.
Si se activa, la extensión puede ser compilada y distribuida por sí misma. De lo contrario, solo puede ser distribuida anexa a otra extensión.
Si declaramos una extensión como un paquete, queda disponible para ser compilada y distribuida.
Para compilar, debemos seleccionar una o más extensiones:

y presionar el botón:

También, es posible compilar presionando el botón:

En algunas ocasiones, se desplegarán modales que irán preguntando si deseamos actualizar versiones. Por último, obtendremos el siguiente formulario:

Los valores por defecto serán los necesarios para distribuir.
Cuando una extensión se compila, se tienen en cuenta tres procesos: copiar los archivos, exportar la estructura de la base de datos y recolectar datos. Para ello, se utilizan tres sistemas: components, db_queries y xdata, respectivamente.
Copiar archivos es sencillo si los tenemos bien organizados. Para ello, utilizamos el concepto de componentes, los componentes serán carpetas con el nombre del alias de la extensión. El compilador sólo tendrá que buscar estas carpetas en los siguientes espacios:
app/
cms/libraries/
cms/plugins/
cms/scripts/
cms/snippets/
media/
vendor/
La estructura de la base de datos incluirá tablas, funciones, procedimientos y disparadores.
Se exportarán de dos modos: como un archivo sql y como uno json. Este último modo, permite modificar las bases de datos de las distribuciones, incluido renombrar tablas y campos.
El compilador va a exportar todas las tablas cuyo nombre comience con el nombre del alias. Por ejemplo, si tenemos la extensión con el alias de nombre posts, exportará la tabla posts y todas las que se llamen posts_*.
A su vez, serán exportadas las funciones y los procedimientos cuyo nombre esté escrito en camelCase y su segundo texto sea el alias. Para el mismo ejemplo anterior, el compilador exportará createPosts o removePostsTags.
Todos los datos serán distribuidos a través de archivos json, ya sea que provengan de la base de datos o de archivos. Esto se hace para agregar capas de procesamiento de datos y seguridad.
Los datos pueden ser: configuraciones, menús, permisos de ususarios, activos, etc.
El sistema que los recolecta y los distribuye se llama xdata.
Un proceso de datos de los más importantes de xdata es el que permite combinar, en la instalación de actualizaciones, los datos existentes, con los datos que trae el paquete de actualización.
Para distribuir una extensión, primero se debe compilar y luego presionar el botón:

Para distribuir una extensión, se debe tener configurada la url de la tienda en línea y el token de acceso del desarrollador.
El registro de cambios (changelog) permite, a la hora de compilar una extensión, calcular automáticamente su número de versión.
En la Administración, debe dirigirse a Configuraciones > Extensiones, seleccionar un registro y presionar el botón:

Se desplegará la lista de cambios de la extensión:

El formulario para tal fin requiere dos campos:
Descripción del cambio.
Informa si el cambio es compatible con las versiones anteriores. De ser compatible, se incrementará la versión menor, de lo contrario, la versión mayor.
El historial de cambios permite renombrar, en las ditribuciones, las tablas y/o campos.
Si modificamos el nombre de una tabla o de un campo, debemos dirigirnos aquí, y registrar ese cambio. Al momento de compilar la extensión, este registro de cambios será exportado junto con la base de datos.
Para realizar esta tarea, se debe seleccionar una extensión, y presionar el botón:

Se desplegará un formulario con todas las tablas y campos de la extensión. A continuación, mostramos por ejemplo la extensión extensions:

*1 En Historia, se puede registrar el cambio de nombre de la tabla a renombrar.
Es posible registrar varios nombres de tablas y campos a renombrar, separándolos por una coma.
Cuando, a partir de una extensión, creamos un paquete, es posible anexar otras extensiones.
Para realizar esta tarea, se debe seleccionar una extensión definida como un paquete, y presionar el botón:

Se desplegará un formulario que permite seleccionar otras extensiones.
Una vez creada una extensión, estamos listos para mostrar un "Hola mundo".
Para empezar, debemos crear una carpeta con el nombre de la extensión en app/. Continuaremos con el ejemplo de la extensión posts, por lo que el directorio será:
app/posts/
Allí crearemos el archivo controlador. Como el punto de acceso será el frontend, tendrá por nombre FrontPostsController.php. Dentro, estará la clase de igual nombre que extiende de la clase Controller, el método público index, que será llamado por defecto y nuestro texto como valor de retorno.
<?php
// app/posts/FrontPostsController.php
class FrontPostsController extends Controller
{
/**
* Index
*/
public function index()
{
return 'Hola mundo';
}
}
Para ver en pantalla el resultado, debemos apuntar a https://sitio-web/posts. También, es posible ver el resultado en la raíz, si configuramos la extensión como predeterminada.
Para utilizar una vista, debemos crear, dentro de la carpeta de la extensión, el archivo views/front/Index.php.
<?php
// app/posts/views/front/Index.php
return 'Hola mundo';
El controlador será:
<?php
// app/posts/FrontPostsController.php
class FrontPostsController extends Controller
{
/**
* Index
*/
public function index()
{
return $this->view();
}
}
Para utilizar un modelo, debemos crear, dentro de la carpeta de la extensión, el archivo models/PostsModel.php. Dentro, estará la clase de igual nombre que extiende de la clase Model, y un método público con un nombre significativo.
<?php
// app/posts/models/PostsModel.php
class PostsModel extends Model
{
/**
*
*/
public function getHelloWord()
{
return [
'message' => 'Hola mundo'
];
}
}
Note que el modelo siempre debe devolver un arreglo.
El controlador será:
<?php
// app/posts/FrontPostsController.php
class FrontPostsController extends Controller
{
/**
* Index
*/
public function index()
{
return $this->view(null, (new PostsModel)->getHelloWord());
}
}
y la vista deberá modificarse, para devolver la variable $message.
<?php
// app/posts/views/front/Index.php
return $message;
Todas las consultas http dinámicas son procesadas por el archivo index.php que se encuentra en la raíz del sitio web.
Este archivo puede dividirse en tres partes: la inclusión de las configuraciones y librerías del sistema, la inicialización del sistema y el procesamiento de la consulta propiamente dicha.
Las librerías y configuraciones que ponen a funcionar el sistema se establecen con dos inclusiones, el archivo de arranque y la librería de autocarga.
include 'bootstrap.php';
include SYSTEM_ABSPATH . SYSTEM_AUTOLOAD;
Este archivo se encuentra a salvo de las actualizaciones y contiene algunas constantes básicas.
define('SYSTEM_ABSPATH', dirname(__FILE__) . DIRECTORY_SEPARATOR);
define('SYSTEM_STORAGE', SYSTEM_ABSPATH . 'storage/');
define('SYSTEM_SETPATH', SYSTEM_STORAGE . 'settings/');
define('SYSTEM_MEDIA_PATH', 'media/');
define('SYSTEM_AUTOLOAD', 'cms/libraries/system/autoload.php');
//define('IS_DEMO', true);
define('GET', INPUT_GET);
define('POST', INPUT_POST);
Entre las constantes se destaca la ruta a la librería de autocarga.
La constante IS_DEMO se utiliza para crear un sitio web "demo", en el que se bloquea cualquier acción sencible.
Este archivo incluye y ejecuta la autocarga de librerías. De manera predeterminada, incluirá la librería básica del framework. Pero, como se dijo anteriormente, ésta puede ser modificada desde el arranque.
El framework contiene otro archivo de autocarga llamado autoload-quickly.php.
El sistema requiere inicializar tres librerías:
$container = Container::getInstance();
$container->get('system');
$container->get('debugger');
En principio, debemos iniciar el contenedor de dependencias, encargado de crear instancias y/o almacenarlas.
Luego, se almacenan las únicas dos librerías del sistema que requieren ser inicializadas.
También, en esta parte se inicializa el perfilador del código cuando esta activado desde las configuraciones.
El sistema para procesar la consulta está basado en una implementación de las PSR.
Primero, con un objeto ServerRequest se representa la consulta. Esta consulta se pasa a un manejador de consultas RequestHandler, que a su vez, la pasará a través varios Middleware hasta que uno de ellos devuelva un objeto Response.
En nuestro framework, el objeto RequestHandler, encargado de ejecutar los middlewares, se llama Runner.
$request = (new ServerRequestFactory)->createServerRequest();
$handler = new Runner();
$handler->add('router');
$response = $handler->handle($request);
En principio, el único middleware que se agrega es el RouterMiddleware, que se encargará de analizar la url, y devolver un controlador o null si la url no se corresponde con un recurso del servidor.
Una vez obtenida la respuesta, ésta debe ser emitida a la consola sapi. Para ello, se utiliza la clase SapiEmitter.
(new SapiEmitter)->emit($response);
A continuación, se listan las extensiones que son parte del núcleo de JuncoCMS.
Genera una interfáz completa para administrar registros.

Combina una tabla, donde se listan los registros, con un filtro de búsquedas y botones de acción y de paginación.
Para generar una lista, requiere una parte del lado del servidor, compuesta de varios fragmentos. Y una parte del lado del usuario, compuesta de los controles en javascript.
Vincula una URL con un recurso del sitio web. En este sentido, se encarga de generar una URL capaz de representar un recurso del servidor, y de entregar dicho recurso cuando un usuario apunta a su URL.
El recurso puede ser un archivo HTML o cualquier otro tipo de archivo que sea generado de forma dinámica, es decir, utilizando PHP.
La librería permite funciones avanzadas, como las URL amigables y el reemplazo del nombre del componente y de la tarea.
La vinculación entre url y el recurso se realizará de modo automático siguiendo algunas convenciones. Internamente, los valores que serán considerados son:
access_point: punto de acceso.component: apunta a un componente de la aplicación.task: la tarea que debe ejecutar dicho componente.format: el formato de salida, puede ser una plantilla HTML, un texto, un json, un modal, etc.
Una aplicación puede tener distintos puntos de acceso, cada uno de ellos tendrá sus propios propósitos. Algunos de los puntos de acceso más comunes son:
| Valor | Descripción |
|---|---|
front |
muestra datos, de acceso público. |
admin |
administra los datos. De acceso único para administradores. |
my |
administra los datos. De acceso único para usuarios registrados y con permisos. |
audit |
administra los datos. De acceso único para usuarios administradores y con permisos. |
api |
interfaz de programación de aplicaciones. |
console |
interfaz de consola. |
Si no se declara el punto de acceso, el framework utilizará por defecto el valor: front.
Un componente es un punto de acceso en donde la extensión permite ejecutar distintos tipos de tareas. En este aspecto, y para los fines prácticos, un punto de acceso más un componente hacen referencia a un controlador.
El componente por defecto tiene el nombre de la extensión a la que deseamos acceder. Para acceder a otro componente dentro de la extensión, debemos agregar un punto y el nombre del subcomponente al que deseamos acceder.
$component = "{{extension}}.{{subcomponent}}";
Los componentes por defecto serán: console para el acceso por consola, un valor configurable para el acceso a través del frente (front) y para el resto de los puntos de acceso será home.
La tarea a ejecutar dentro de un controlador. En caso de no indicar un valor, el framework utilizará el valor: index.
Este valor debe ser pasado como argumento. En el framawork ha perdido importancia con el tiempo. Actualmente, se utiliza para dar respuestas correctas en caso de tener errores antes de cargar la vista. Algunas aplicaciones lo utilizan para decidir el tipo de respuesta que devolverá.
Es posible registrar Urls que no siguen las convenciones del framework. Para ello, en la aplicación debe crearse un archivo:
app/{{extension}}/routes/{{Accesspoint}}Routes.php
y en él se podrá realizar todos los registros de Urls que se desee. Las funciones para registrar Uri son: Router::route y Router::routeWith.
Las uri pueden incluir parámetros:
Router::route('articles/{id:int}', 'show');
Note que se agregó un parámetro de nombre id, y que además es del tipo integer. Actualmente, los tipos soportados son:
int un enteroid un entero positivostring cualquier cadena, hasta una barra /.* el resto de la cadena, incluida las barras /.Si el parámetro es opcional, se le agrega el modificador de pregunta ?.
Router::route('articles/day/{date?}', 'day');
Los parámetros asociativos se indican del siguiete modo:
Router::route('articles/page=/{page_number?}', 'list');
Las funciones de registros de Uri permiten agregar identificadores como tercer parámetro.
Router::route('articles/{id:int}', 'show', '#show');
Luego, este identificador puede ser utilizado en la función que crea una Url.
url('#show', ['id' => 1]);
Esto será válido dentro del componente de la aplicación. Sin embargo, si queremos utilizar el identificador para crear una Url fuera del ámbito de ésta, es necesario anteponer el identificador del componente, compuesto por #{accesspoint}.{component}.
url('#front.articles#show', ['id' => 1]);
La extensión XJs es un formato estándar de salida para consultas asincrónicas del framework. Se utiliza para realizar tareas en segundo plano, como ser, la carga de un formulario.
La respuesta consiste en un json con un formato similar al siguiente:
{
"message": "Message in response",
"code": 0,
"data": {},
"error": "",
"profiler": ""
}
Los tres primeros valores son a discreción de la aplicación. Los últimos dos, son de uso interno para los casos requeridos.
Es una cadena, supone un texto en respuesta.
Es un entero, supone una representación numérica de la respuesta. Si bien no tiene reglas, se suele usar valores positivos para las respuestas satisfactorias y negativos o el cero para los fallos.
Cualquier tipo de dato que pueda representarse con json.