Skip to content

Instantly share code, notes, and snippets.

@mfalemany
Last active April 18, 2026 23:10
Show Gist options
  • Select an option

  • Save mfalemany/09ef2a9fd1b78e2b55dfbf679e22d28b to your computer and use it in GitHub Desktop.

Select an option

Save mfalemany/09ef2a9fd1b78e2b55dfbf679e22d28b to your computer and use it in GitHub Desktop.
Documentación Toba 2.7

Manual del Desarrollador — SIU Toba 2.7.x

Guía de referencia para el desarrollo de nuevas funcionalidades en el framework SIU Toba.


Tabla de Contenidos

  1. Arquitectura General
  2. Flujo de Ejecución de un Request
  3. Componentes Principales
  4. CI — Controlador de Interface
  5. CN — Controlador de Negocio
  6. Componentes de UI
  7. Persistencia y Base de Datos
  8. Servicios Globales — Clase toba
  9. Navegación y Vínculos
  10. Patrones Frecuentes
  11. Convenciones de Nombres
  12. Estructura de Directorios
  13. Referencia de Archivos Clave

1. Arquitectura General

SIU Toba es un framework PHP orientado a componentes. Cada "ítem" del menú de la aplicación corresponde a un par:

  • CI (toba_ci): Controlador de Interface. Maneja la presentación, la interacción con el usuario, y el ciclo de vida de la pantalla.
  • CN (toba_cn): Controlador de Negocio. Encapsula la lógica de negocio y el acceso a datos.

Los componentes de UI (formularios, grillas, filtros, etc.) son dependencias del CI. El CI los configura y responde a sus eventos a través de callbacks con nombres convencionales.

Request HTTP
    └── toba_nucleo (kernel, singleton)
            └── toba_solicitud_web
                    ├── CI (Controlador de Interface)
                    │       ├── dep('formulario')  → toba_ei_formulario
                    │       ├── dep('cuadro')      → toba_ei_cuadro
                    │       └── dep('pantalla')    → toba_ei_pantalla
                    └── CN (Controlador de Negocio)
                            └── dep('datos')       → toba_datos_tabla / toba_ap_tabla_db

2. Flujo de Ejecución de un Request

Cada request web pasa por las siguientes etapas, en orden:

1. Construcción (__construct)

  • Se recupera memoria sincronizada de sesión (cargar_memoria()).
  • Se cargan las propiedades prefijadas con s__ para restaurar el estado.
  • Los componentes no deben lógica de negocio aquí (el constructor del CI es final).

2. Inicialización (inicializar())

  • Se restaura el estado de sesión desincronizado (recuperar_estado_sesion()).
  • Se llama a ini__operacion() sólo la primera vez que se accede a la operación.
  • Se llama a ini() en cada request.

3. Disparo de eventos (disparar_eventos())

  • Se atienden los eventos generados en el request anterior (clic de botón, selección de fila, etc.).
  • Los eventos de las dependencias se propagan recursivamente.
  • Luego se llama a post_eventos().
  • Finalmente se controla si hubo cambio de pantalla/solapa.

4. Servicios (procesar_servicios())

  • Se invocan los callbacks conf__<id_componente>() para configurar cada dependencia antes de renderizar.
  • Se genera el HTML/PDF/XML de respuesta.

5. Destrucción (destruir())

  • Se llama a fin().
  • Se persiste la memoria y el estado de sesión.
  • Se destruyen los componentes hijos recursivamente.

3. Componentes Principales

Clase Prefijo Rol
toba_ci ci Controlador de Interface (presentación)
toba_cn cn Controlador de Negocio (lógica)
toba_ei_formulario form Formulario de edición
toba_ei_cuadro cuadro Grilla/tabla de datos
toba_ei_filtro Filtro de búsqueda
toba_ei_pantalla Contenedor de solapas/tabs
toba_ei_arbol Vista de árbol
toba_datos_tabla Tabla de datos en memoria
toba_ap_tabla_db Patrón de acceso a tabla DB

4. CI — Controlador de Interface

4.1 Constructor final y cómo definir propiedades tipadas

El constructor de toba_ci está declarado como final:

// En toba_ci.php (NO modificar):
final function __construct($id)
{
    $this->set_propiedades_sesion(array('_ini_operacion','_dependencias_ci', '_metodos_ajax'));
    parent::__construct($id);
    $this->_nombre_formulario = "formulario_toba";
}

Esto significa que no podés sobreescribir el constructor en tu CI. En su lugar, el framework provee las siguientes alternativas para inicializar propiedades:

Opción A: ini__operacion() — se ejecuta UNA vez al inicio de la operación

Ideal para inicializar propiedades que deben persistir mientras el usuario trabaja en esa operación. Si necesitás que una propiedad apunte a un objeto o tenga un valor inicial específico, hacelo aquí:

class ci_mis_datos extends toba_ci
{
    protected $s__cliente;   // persistirá en sesión automáticamente

    function ini__operacion()
    {
        // Se ejecuta solo la primera vez que se accede a este item
        $this->s__cliente = array('id' => null, 'nombre' => '');
    }
}

Opción B: ini() — se ejecuta en CADA request

Ideal para propiedades que se recalculan o regeneran en cada pedido (por ejemplo, servicios, DAOs, o cualquier objeto que no se pueda serializar):

class ci_mis_datos extends toba_ci
{
    // NO usar s__ aquí si el objeto no es serializable
    protected $servicio_externo;

    function ini()
    {
        // Se ejecuta en cada request; el objeto se crea de nuevo cada vez
        $this->servicio_externo = new mi_servicio_rest();
    }
}

Opción C: Inicialización lazy en conf__ o en métodos de negocio

Si la propiedad sólo se necesita en ciertos contextos, podés inicializarla a demanda:

class ci_mis_datos extends toba_ci
{
    protected $repo;

    function get_repo()
    {
        if (! isset($this->repo)) {
            $this->repo = new repositorio_clientes();
        }
        return $this->repo;
    }

    function conf__cuadro(toba_ei_cuadro $cuadro)
    {
        $cuadro->set_datos($this->get_repo()->listar_todos());
    }
}

Opción D: Para objetos serializables en sesión — toba_serializar_propiedades

Si tenés un objeto que necesitás persistir en sesión y además tiene referencias a componentes Toba, podés hacer que herede de toba_serializar_propiedades. Esa clase implementa __sleep() y __wakeup() para serializar correctamente.

class mi_contexto extends toba_serializar_propiedades
{
    public $cliente_id;
    public $filtro_activo;
    // Los componentes toba se serializan con lógica especial
}

class ci_mis_datos extends toba_ci
{
    protected $s__contexto;  // Se guardará en sesión automáticamente

    function ini__operacion()
    {
        $this->s__contexto = new mi_contexto();
        $this->s__contexto->cliente_id = null;
    }
}

Importante: Los componentes Toba (toba_ci, toba_cn, etc.) no son serializables directamente. Si intentás guardarlos en una propiedad s__, obtendrás un error. Para referencias a componentes, usá el método ini() para recrearlos en cada request, o usá toba_serializar_propiedades.


4.2 Persistencia de propiedades en sesión

Toba ofrece dos mecanismos para persistir el estado entre requests:

Prefijo s__ (recomendado)

Cualquier propiedad cuyo nombre empiece con s__ se persiste automáticamente en sesión. No se necesita ninguna configuración adicional.

class ci_ejemplo extends toba_ci
{
    protected $s__filtro;          // se guarda y restaura automáticamente
    protected $s__pagina_actual;   // ídem
    protected $s__datos_form;      // ídem

    function evt__formulario__modificacion($datos)
    {
        $this->s__datos_form = $datos;  // en el próximo request estará disponible
    }
}

set_propiedades_sesion() (alternativa explícita)

Podés marcar propiedades con nombres arbitrarios para persistencia explícita:

class ci_ejemplo extends toba_ci
{
    protected $pasadas_por_solapa;

    function ini__operacion()
    {
        $this->pasadas_por_solapa = array('1' => 0, '2' => 0);
    }

    // Método OBSOLETO pero todavía funciona en 2.7.x:
    function mantener_estado_sesion()
    {
        $propiedades = parent::mantener_estado_sesion();
        $propiedades[] = 'pasadas_por_solapa';
        return $propiedades;
    }

    // Alternativa moderna (desde toba_componente):
    // llamar $this->set_propiedades_sesion(array('pasadas_por_solapa')) en ini()
}

Regla práctica: Usá s__ como prefijo siempre que sea posible. Es la forma idiomática y más clara de indicar que una propiedad es parte del estado de sesión.


4.3 Ciclo de vida completo

Constructor (final, no sobreescribir)
    ↓
ini__operacion()   ← Solo la 1ra vez; ideal para inicializar estado persistente
    ↓
ini()              ← Cada request; ideal para crear objetos no-serializables
    ↓
[disparo de eventos del request anterior]
evt__<comp>__<evento>()  ← Handler de evento
    ↓
post_eventos()     ← Hook posterior a todos los eventos
    ↓
[cambio de pantalla si aplica]
evt__<pantalla>__salida()
evt__<pantalla>__entrada()
    ↓
conf__<comp>()     ← Configura cada componente antes de renderizar
    ↓
fin()              ← Justo antes de destruir; estado ya en sesión
    ↓
destruir()         ← Framework persiste sesión y destruye objetos

4.4 Callbacks de configuración conf__

El método conf__<id_componente>() se ejecuta en la etapa de servicios, justo antes de que el componente genere su HTML. Es el lugar para:

  • Proveer datos al componente.
  • Modificar su apariencia o comportamiento.
  • Configurar eventos dinámicamente.
class ci_listado extends toba_ci
{
    protected $s__filtro;

    // Retornando un array, se setean los datos del componente directamente
    function conf__cuadro()
    {
        return $this->obtener_datos();
    }

    // Recibiendo el componente como parámetro (type-hint habilitado)
    function conf__cuadro(toba_ei_cuadro $cuadro)
    {
        $cuadro->set_datos($this->obtener_datos());
        $cuadro->evento('alta')->desactivar();
    }

    // Retornando un array desde conf__ de un formulario
    function conf__form_filtro()
    {
        if (isset($this->s__filtro)) {
            return $this->s__filtro;
        }
    }
    
    // conf sin id de componente: se llama antes de procesar cualquier dependencia
    function conf()
    {
        // Útil para lógica global de la pantalla actual
        if ($this->get_id_pantalla() == 'detalle') {
            $this->pantalla()->eliminar_evento('cancelar');
        }
    }
}

Nota sobre el type-hint: En los métodos conf__, podés recibir el componente como parámetro con type-hint. Esto también funciona para eventos:

function conf__formulario(toba_ei_formulario $form) { ... }
function evt__cuadro__seleccion($fila) { ... }

4.5 Manejo de eventos evt__

Los eventos siguen el patrón evt__<id_componente>__<id_evento>:

class ci_abm extends toba_ci
{
    protected $s__datos;
    protected $s__clave;

    // Evento de modificación de formulario
    function evt__formulario__modificacion($datos)
    {
        $this->s__datos = $datos;
    }

    // Evento de selección de fila en cuadro (recibe la fila seleccionada)
    function evt__cuadro__seleccion($fila)
    {
        $this->s__clave = $fila['id'];
        $this->set_pantalla('detalle');
    }

    // Evento de baja en cuadro
    function evt__cuadro__baja($fila)
    {
        toba::notificacion()->agregar("Eliminado: {$fila['nombre']}", 'info');
    }

    // Evento de filtrar en filtro
    function evt__filtro__filtrar($datos)
    {
        $this->s__filtro = $datos;
    }

    // Evento de entrada a una solapa (id de pantalla = 'edicion')
    function evt__edicion__entrada()
    {
        // El usuario acaba de entrar a la solapa 'edicion'
    }

    // Evento de salida de una solapa
    function evt__edicion__salida()
    {
        // Se puede lanzar una excepción toba_error_usuario para cancelar la navegación
    }

    // Evento propio del CI (sin componente)
    function evt__guardar()
    {
        $this->cn()->procesar($this->s__datos);
        toba::notificacion()->agregar('Guardado correctamente', 'exito');
        $this->set_pantalla('listado');
    }
}

Acceso a pantalla, CN, y notificaciones dentro de un evento

function evt__procesar()
{
    // Acceder al CN asociado
    $this->cn()->procesar($this->s__datos);

    // Navegar a otra pantalla/solapa
    $this->set_pantalla('confirmacion');

    // Mostrar notificaciones al usuario
    toba::notificacion()->agregar('Operación exitosa', 'exito');  // 'exito', 'info', 'error'

    // Lanzar error de usuario (muestra mensaje sin romper la app)
    throw new toba_error_usuario("El campo X es obligatorio");
}

4.6 Acceso a dependencias

// Obtener una dependencia por su ID
$this->dep('formulario');            // equivalente a $this->dependencia('formulario')
$this->dep('cuadro');
$this->dep('filtro');

// Verificar si existe una dependencia
$this->existe_dependencia('detalle');

// Acceder a la pantalla actual
$this->pantalla();

// Acceder a una pantalla específica
$this->dep('ci_hijo')->pantalla('nombre_pantalla');

// Operaciones comunes sobre dependencias
$this->dep('formulario')->set_datos($datos);
$this->dep('cuadro')->deseleccionar();
$this->dep('pantalla')->eliminar_evento('cancelar');

// Acceder a un EF específico dentro de un formulario
$this->dep('formulario')->ef('nombre_campo')->desactivar();
$this->dep('formulario')->ef('nombre_campo')->set_estado($valor);

4.7 Métodos AJAX ajax__

Para operaciones asíncronas, definí métodos con el prefijo ajax__. Son invocados desde JavaScript:

class ci_busqueda extends toba_ci
{
    // PHP: método invocado por AJAX
    function ajax__buscar_cliente($parametros, toba_ajax_respuesta $respuesta)
    {
        $datos = $this->cn()->buscar($parametros['texto']);
        $respuesta->set($datos);
    }

    // PHP: retornar HTML
    function ajax__cargar_detalle($id, toba_ajax_respuesta $respuesta)
    {
        $html = '<div>Detalle del elemento ' . htmlspecialchars($id) . '</div>';
        $respuesta->set($html);
    }

    // JS: cómo invocarlo desde extender_objeto_js()
    function extender_objeto_js()
    {
        $id_js = toba::escaper()->escapeJs($this->objeto_js);
        echo "
            {$id_js}.evt__btn_buscar = function() {
                var params = { texto: this.dep('form').ef('busqueda').get_estado() };
                // Llamada AJAX: método, parámetros, contexto, callback
                this.ajax('buscar_cliente', params, this, this.mostrar_resultados);
                return false; // evita el submit normal
            };

            {$id_js}.mostrar_resultados = function(datos) {
                // datos es lo que se pasó a $respuesta->set(...)
                console.log(datos);
            };
        ";
    }
}

Tipos de llamadas AJAX disponibles:

  • this.ajax(metodo, params, contexto, callback) — retorna datos serializados
  • this.ajax_html(metodo, params, nodo_html) — inyecta HTML en un nodo
  • this.ajax_cadenas(metodo, params, contexto, callback) — múltiples cadenas nombradas

5. CN — Controlador de Negocio

El CN encapsula la lógica de negocio. También tiene constructor final.

class cn_clientes extends toba_cn
{
    protected $s__datos;

    function ini()
    {
        // Inicialización en cada request
    }

    // Método de negocio invocado desde el CI
    function procesar($datos)
    {
        // Transacción abierta automáticamente por el framework
        $this->dep('tabla_clientes')->set_datos($datos);
    }

    // Validación de datos
    function evt__validar_datos()
    {
        if (empty($this->dep('tabla_clientes')->get_datos())) {
            throw new toba_error_usuario('No hay datos para procesar');
        }
    }

    // Procesamiento específico (se ejecuta dentro de una transacción)
    function evt__procesar_especifico()
    {
        // La sincronización con la DB ocurre automáticamente en destruir()
    }
}

Ciclo de procesamiento del CN:

  1. CI llama $this->cn()->procesar() o $this->cn()->cancelar()
  2. procesar() abre transacción → llama evt__validar_datos()evt__procesar_especifico() → cierra transacción → limpiar_memoria()
  3. cancelar() limpia la memoria y el estado

6. Componentes de UI

6.1 toba_ei_formulario

El formulario presenta campos editables (EF - Elementos de Formulario).

// En el CI:
function conf__formulario(toba_ei_formulario $form)
{
    // Proveer datos (array asociativo campo => valor)
    $form->set_datos(array(
        'nombre'   => 'Juan',
        'apellido' => 'Pérez',
        'activo'   => 1,
    ));

    // Configuraciones opcionales
    $form->set_ancho_etiqueta('200px');
    $form->cambiar_layout();  // cambia disposición de columnas
}

// Alternativa: retornar el array directamente
function conf__formulario()
{
    return array('nombre' => 'Juan', 'apellido' => 'Pérez');
}

// Evento de modificación (se dispara cuando el usuario cambia algo y confirma)
function evt__formulario__modificacion($datos)
{
    // $datos es array asociativo con todos los campos del formulario
    $this->s__datos_form = $datos;
}

Operaciones sobre EFs individuales:

function conf__formulario(toba_ei_formulario $form)
{
    $form->ef('codigo')->desactivar();          // campo solo lectura
    $form->ef('descripcion')->ocultar();        // campo invisible
    $form->ef('importe')->set_estado(1500);     // setear valor puntual
    $form->ef('tipo')->agregar_opcion('X', 'Otro tipo');
}

6.2 toba_ei_cuadro

La grilla muestra datos tabulares con soporte de paginado, ordenamiento y eventos por fila.

function conf__cuadro(toba_ei_cuadro $cuadro)
{
    // Datos: array de arrays asociativos
    $datos = array(
        array('id' => 1, 'nombre' => 'Rectorado', 'activo' => 1),
        array('id' => 2, 'nombre' => 'Secretaría', 'activo' => 0),
    );
    $cuadro->set_datos($datos);

    // Configuraciones opcionales
    $cuadro->evento('baja')->set_alineacion_pre_columnas(true);
    $cuadro->evento('seleccion')->desactivar();
}

// Alternativa: retornar el array directamente
function conf__cuadro()
{
    return $this->cn()->listar();
}

// Evento de selección de fila
function evt__cuadro__seleccion($fila)
{
    // $fila es el array asociativo de la fila seleccionada
    $this->s__id_seleccionado = $fila['id'];
}

// Evento de baja
function evt__cuadro__baja($fila)
{
    $this->cn()->eliminar($fila['id']);
    toba::notificacion()->agregar('Registro eliminado', 'info');
}

// Evento de ordenamiento (retornar true para que Toba NO haga el sort automático)
function evt__cuadro__ordenar($param)
{
    // $param['columna'], $param['sentido'] ('asc' o 'desc')
    $this->s__orden = $param;
    return true;  // el CI manejará el orden
}

6.3 toba_ei_filtro

El filtro es un formulario especial para búsqueda.

class ci_listado extends toba_ci
{
    protected $s__filtro;

    // Datos actuales del filtro para pre-cargar
    function conf__filtro()
    {
        if (isset($this->s__filtro)) {
            return $this->s__filtro;
        }
    }

    // El usuario aplicó el filtro
    function evt__filtro__filtrar($datos)
    {
        $this->s__filtro = $datos;
    }

    // El usuario limpió el filtro
    function evt__filtro__cancelar()
    {
        unset($this->s__filtro);
    }

    // El cuadro usa los datos filtrados
    function conf__cuadro(toba_ei_cuadro $cuadro)
    {
        $datos = $this->cn()->listar($this->s__filtro);
        $cuadro->set_datos($datos);
    }
}

6.4 toba_ei_pantalla y solapas

La pantalla es el contenedor de solapas (tabs).

class ci_abm extends toba_ci
{
    function conf()
    {
        // Ocultar/mostrar solapas dinámicamente
        $this->pantalla()->tab('detalle')->ocultar();
        $this->pantalla()->tab('listado')->mostrar();

        // Desactivar una solapa (se ve pero no se puede clickear)
        $this->pantalla()->tab('avanzado')->desactivar();

        // Cambiar etiqueta e imagen de una solapa
        $this->pantalla()->tab('listado')->set_etiqueta('Resultados');
        $this->pantalla()->tab('listado')->set_imagen('buscar.png');

        // Eliminar un evento de la pantalla actual
        $this->pantalla()->eliminar_evento('cancelar');
    }

    // Navegar programáticamente a una solapa
    function evt__cuadro__seleccion($fila)
    {
        $this->s__seleccionado = $fila;
        $this->set_pantalla('detalle');  // nombre de la solapa destino
    }

    // Obtener la solapa actualmente visible
    function conf__cuadro()
    {
        $pantalla_actual = $this->get_id_pantalla();
        // ...
    }

    // Wizard: navegar hacia adelante/atrás
    function conf()
    {
        if ($this->wizard_avanza()) {
            // navegando hacia adelante
        }
    }
}

Eventos de entrada/salida de solapas:

// Cuando el usuario entra a la solapa 'detalle'
function evt__detalle__entrada()
{
    // Inicializar formulario de detalle
}

// Cuando el usuario intenta salir de 'detalle'
function evt__detalle__salida()
{
    // Validar antes de salir; lanzar excepción para cancelar
    if ($this->hay_cambios_sin_guardar()) {
        throw new toba_error_usuario('Hay cambios sin guardar');
    }
}

6.5 toba_ei_arbol

function conf__arbol()
{
    return array(
        array('id' => 1, 'padre' => null, 'nombre' => 'Raíz'),
        array('id' => 2, 'padre' => 1,    'nombre' => 'Hijo 1'),
        array('id' => 3, 'padre' => 1,    'nombre' => 'Hijo 2'),
    );
}

function evt__arbol__seleccion($nodo)
{
    $this->s__nodo_id = $nodo['id'];
}

7. Persistencia y Base de Datos

toba_datos_tabla

Tabla de datos en memoria que rastrea cambios (INSERT/UPDATE/DELETE):

// Dentro del CN:
$tabla = $this->dep('tabla_clientes');

$tabla->cargar();                           // carga desde DB
$filas = $tabla->get_datos();              // array de filas
$tabla->set_datos($nuevas_filas);          // reemplaza todo
$tabla->agregar_fila(array('nombre'=>'X')); // INSERT pendiente
$tabla->modificar_fila(0, array('nombre'=>'Y')); // UPDATE pendiente
$tabla->borrar_fila(0);                    // DELETE pendiente
// Al destruir el CN, los cambios se sincronizan con la DB

toba_db — Acceso directo a base de datos

// Obtener conexión a la fuente de datos por defecto
$db = toba::db();

// A una fuente específica
$db = toba::db('nombre_fuente');

// Ejecutar consulta
$resultado = $db->consultar("SELECT * FROM clientes WHERE activo = 1");

// Con parámetros (usar siempre para evitar SQL injection)
$resultado = $db->consultar(
    "SELECT * FROM clientes WHERE id = $1",
    array($id)
);

// Ejecutar acción (INSERT/UPDATE/DELETE)
$db->ejecutar("UPDATE clientes SET activo = $1 WHERE id = $2", array(0, $id));

// Transacciones manuales
$db->abrir_transaccion();
try {
    $db->ejecutar("...");
    $db->cerrar_transaccion();
} catch (Exception $e) {
    $db->abortar_transaccion();
    throw $e;
}

8. Servicios Globales — Clase toba

La clase toba es una fachada estática con acceso a todos los servicios del framework:

// Notificaciones al usuario
toba::notificacion()->agregar('Operación exitosa', 'exito');
toba::notificacion()->agregar('Atención: revisar datos', 'info');
toba::notificacion()->agregar('Error al procesar', 'error');

// Logger
toba::logger()->debug('Mensaje de debug', 'mi_categoria');
toba::logger()->info('Información', 'mi_categoria');
toba::logger()->error('Error grave', 'mi_categoria');

// Usuario actual
$usuario = toba::usuario();
$login   = $usuario->get_login();
$nombre  = $usuario->get_nombre();

// Verificar permisos
if (toba::derechos()->tiene_permiso('ABM_CLIENTES')) { ... }

// Proyecto
$id_proyecto = toba::proyecto()->get_id();
$param = toba::proyecto()->get_parametro('mi_parametro');

// Instalación
if (toba::instalacion()->es_produccion()) { ... }

// Memoria de sesión (baja nivel)
toba::memoria()->set_parametro('mi_clave', $valor);
$valor = toba::memoria()->get_parametro('mi_clave');

// Escape seguro para output (anti-XSS)
echo toba::escaper()->escapeHtml($texto);
echo toba::escaper()->escapeJs($texto);

// Generador de vínculos
$url = toba::vinculador()->get_url_operacion('mi_proyecto', 'id_item');

9. Navegación y Vínculos

// Generar una URL a otro ítem
$url = toba::vinculador()->get_url_operacion('proyecto_id', 'item_id');

// Con parámetros pasados por canal
toba::vinculador()->set_parametro_canal('cliente_id', $id);
$url = toba::vinculador()->get_url_operacion('proyecto_id', 'item_id');

// Leer parámetros recibidos por canal en el CI destino
function ini__operacion()
{
    if (isset($this->_canal_recibidos)) {
        $this->s__cliente_id = $this->_canal_recibidos['cliente_id'];
    }
}

// Activar navegación por AJAX (toda la app navega sin recargar página)
toba_ci::set_navegacion_ajax(true);
// (se llama típicamente desde el CI principal o contexto de ejecución)

10. Patrones Frecuentes

ABM clásico (Alta-Baja-Modificación)

class ci_abm_ejemplo extends toba_ci
{
    protected $s__clave;
    protected $s__modo;   // 'alta' | 'modificacion'

    // LISTADO
    function conf__cuadro()
    {
        return $this->cn()->listar();
    }

    function evt__cuadro__alta()
    {
        $this->s__modo = 'alta';
        $this->s__clave = null;
        $this->set_pantalla('formulario');
    }

    function evt__cuadro__modificacion($fila)
    {
        $this->s__modo = 'modificacion';
        $this->s__clave = $fila['id'];
        $this->set_pantalla('formulario');
    }

    function evt__cuadro__baja($fila)
    {
        $this->cn()->eliminar($fila['id']);
    }

    // FORMULARIO
    function conf__formulario(toba_ei_formulario $form)
    {
        if ($this->s__modo == 'modificacion') {
            $form->set_datos($this->cn()->get($this->s__clave));
        }
    }

    function evt__formulario__modificacion($datos)
    {
        $this->cn()->guardar($datos, $this->s__clave, $this->s__modo);
        $this->set_pantalla('listado');
    }

    function evt__cancelar()
    {
        $this->set_pantalla('listado');
    }
}

Filtro + Cuadro

class ci_busqueda extends toba_ci
{
    protected $s__filtro;

    function conf__filtro()
    {
        return $this->s__filtro;
    }

    function evt__filtro__filtrar($datos)
    {
        $this->s__filtro = $datos;
    }

    function evt__filtro__cancelar()
    {
        unset($this->s__filtro);
    }

    function conf__cuadro()
    {
        return $this->cn()->buscar($this->s__filtro);
    }
}

Wizard de múltiples pasos

class ci_wizard extends toba_ci
{
    protected $s__paso1_datos;
    protected $s__paso2_datos;

    function conf()
    {
        $pantalla = $this->get_id_pantalla();
        // Lógica de skip de pasos
        if ($pantalla == '3' && condicion_skip()) {
            $paso = $this->wizard_avanza() ? 4 : 2;
            $this->set_pantalla($paso);
        }
        // Cambiar etiqueta del botón "Siguiente" en el último paso
        if ($pantalla == '4') {
            $this->pantalla()->evento('cambiar_tab__siguiente')->set_etiqueta('Finalizar');
        }
    }

    function evt__form_paso1__modificacion($datos)
    {
        $this->s__paso1_datos = $datos;
    }

    function evt__form_paso2__modificacion($datos)
    {
        $this->s__paso2_datos = $datos;
        $this->cn()->procesar($this->s__paso1_datos, $this->s__paso2_datos);
    }
}

11. Convenciones de Nombres

Concepto Convención Ejemplo
Clase CI ci_<nombre> ci_listado_clientes
Clase CN cn_<nombre> cn_clientes
Propiedad de sesión s__<nombre> $s__datos_form
Callback de config conf__<id_dep> conf__formulario
Handler de evento evt__<dep>__<evento> evt__cuadro__seleccion
Método AJAX ajax__<nombre> ajax__calcular_total
Callback ini ini__operacion / ini
Callback fin fin
Callback post-eventos post_eventos
Archivos PHP snake_case ci_listado_clientes.php
Métodos snake_case get_datos_cliente()

12. Estructura de Directorios

/mi_proyecto/
├── www/
│   └── aplicacion.php          ← Punto de entrada HTTP
├── php/
│   ├── componentes/            ← Clases CI, CN y personalizaciones de EI
│   │   ├── mi_ci/
│   │   │   ├── ci_ejemplo.php
│   │   │   └── cn_ejemplo.php
│   │   └── ...
│   ├── lib/                    ← Librerías propias del proyecto
│   └── mi_proyecto_autoload.php
├── metadatos/                  ← Definiciones de componentes (editor Toba)
│   ├── items/
│   ├── componentes/
│   └── ...
├── proyecto.ini                ← Configuración del proyecto
└── instalacion/

13. Referencia de Archivos Clave

Framework Core

Archivo Descripción
php/nucleo/toba_nucleo.php Kernel principal (singleton)
php/nucleo/toba.php Fachada estática de servicios
php/nucleo/toba_solicitud_web.php Procesamiento del request HTTP
php/toba_autoload.php Mapa de clases → archivos

Componentes Base

Archivo Descripción
php/nucleo/componentes/toba_componente.php Clase base de todos los componentes
php/nucleo/componentes/interface/toba_ei.php Base de elementos de interface
php/nucleo/componentes/interface/toba_ci.php Controlador de Interface (leer completo)
php/nucleo/componentes/negocio/toba_cn.php Controlador de Negocio
php/nucleo/componentes/interface/toba_ei_pantalla.php Pantalla/solapas
php/nucleo/componentes/interface/toba_ei_formulario.php Formulario
php/nucleo/componentes/interface/toba_ei_cuadro.php Grilla
php/nucleo/componentes/interface/toba_ei_filtro.php Filtro

Persistencia

Archivo Descripción
php/nucleo/componentes/persistencia/toba_datos_tabla.php Tabla de datos en memoria
php/nucleo/componentes/persistencia/toba_ap_tabla_db.php Patrón acceso a DB
php/lib/db/toba_db.php Abstracción de base de datos
php/nucleo/lib/toba_serializar_propiedades.php Serialización de objetos en sesión

Ejemplos de Referencia

Archivo Descripción
proyectos/toba_referencia/php/componentes/ci/ci_wizard.php Wizard multi-paso
proyectos/toba_referencia/php/componentes/ci/ci_solapas.php Manejo de solapas con estado
proyectos/toba_referencia/php/componentes/ci/ci_tabs.php Ocultar/mostrar tabs dinámicamente
proyectos/toba_referencia/php/componentes/ei_filtro - ei_cuadro/extension_ci.php Filtro + cuadro completo
proyectos/toba_referencia/php/componentes/ajax/ci_ajax.php Todos los patrones AJAX
proyectos/toba_referencia/php/componentes/eventos/extension_ci.php Manejo avanzado de eventos

Notas Adicionales

Lanzar errores de usuario

// Muestra un mensaje al usuario sin abortar la operación de forma fatal
throw new toba_error_usuario("El campo X es obligatorio");

// Error técnico (aborta y muestra error)
throw new toba_error("Error interno grave");

Datos pasados entre solapas (canal)

// En el CI origen: pasar datos al CI destino via canal
toba::memoria()->set_parametro(
    apex_hilo_qs_canal_obj . 'id_ci_destino',
    array('cliente_id' => 5)
);

// En el CI destino: leer en ini__operacion
function ini__operacion()
{
    if (isset($this->_canal_recibidos)) {
        $this->s__cliente_id = $this->_canal_recibidos['cliente_id'];
    }
}

Formateo de columnas en cuadro

class mi_formateo extends toba_formateo
{
    function formato_moneda($valor)
    {
        return '$ ' . number_format($valor, 2, ',', '.');
    }
}

// En conf__cuadro:
function conf__cuadro(toba_ei_cuadro $cuadro)
{
    $cuadro->set_formateo_columna('importe', 'moneda', 'mi_formateo');
    $cuadro->set_datos($this->cn()->listar());
}

Extender JavaScript del componente

function extender_objeto_js()
{
    // $this->objeto_js contiene el ID JS del componente actual
    $id_js = toba::escaper()->escapeJs($this->objeto_js);
    echo "
        {$id_js}.evt__mi_boton = function() {
            // lógica JS del botón
            return false; // cancelar submit normal
        };
    ";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment