Crear un Mantenedor desde Cero
GuΓa paso a paso para crear la pantalla de administraciΓ³n de Trazas sin usar el generador de cΓ³digo, entendiendo cada capa de la arquitectura.
ΒΏQuΓ© es un Mantenedor?
Un mantenedor es una pantalla que permite al usuario administrar los registros de una tabla de base de datos: listar, crear, editar y eliminar. Es el CRUD bΓ‘sico del sistema.
En este proyecto, un mantenedor completo se compone de tres capas de cΓ³digo PHP mΓ‘s una capa de JavaScript en el frontend:
| Capa | QuΓ© hace | Archivos involucrados |
|---|---|---|
| BD Base de Datos | Tabla SQL Server donde viven los datos | Tools.Traza |
| DAO Acceso a Datos | Mapea la tabla a PHP, expone operaciones CRUD | Entity, Dao |
| BLL LΓ³gica de Negocio | DTOs, servicios de mapeo y reglas de negocio | Dto, Svc, BO |
| CTRL Controllers | Reciben peticiones HTTP y devuelven respuestas | Controller web + Controller API |
| VIEW Vistas PHP | Renderizan el HTML usando componentes del framework | verView, verGridView, popupView, verGridT |
| JS JavaScript | LΓ³gica del frontend: llamadas AJAX, validaciones, eventos | TrazaSvc.js, traza.js |
bin/gen.sh) que crea automΓ‘ticamente la mayorΓa de estos archivos. Esta guΓa te enseΓ±a a hacerlo manualmente para que entiendas cada pieza.
Arquitectura en Capas
El sistema sigue una arquitectura en capas estricta. Ninguna capa puede saltarse a otra: la PresentaciΓ³n solo habla con BLL, BLL solo habla con DAO.
Regla de dependencias
| Capa | Conoce | NO conoce |
|---|---|---|
| PresentaciΓ³n | DTOs | Entities, DAOs directamente |
| BLL | DTOs + Entities | SQL, conexiones directas |
| DAO | Entities | DTOs, lΓ³gica de negocio |
Flujo de una operaciΓ³n
Usuario β Controller β BO β Svc β Dao β SQL Server
β DTO β DTO β
Archivos a Crear
Para la tabla Tools.Traza necesitas crear los siguientes archivos, en este orden:
public/ βββ services/mantenedores/Tools/ β βββ TrazaSvc.js β Cliente AJAX del mantenedor β βββ TrazaSvcT.js β Extensiones JS (puede quedar vacΓo) βββ scripts/mantenedores/tools/ βββ traza.js β Eventos y lΓ³gica frontend βββ trazaT.js β Extensiones (puede quedar vacΓo)
T. La clase base contiene la estructura que podrΓa regenerarse; el Trait contiene las extensiones manuales que nunca se deben perder. Si no tienes extensiones, el Trait queda vacΓo pero siempre debe existir.
DAO Entity β Traza.php
La Entity es un objeto PHP plano (POCO) que representa exactamente una tabla de base de datos. Sus propiedades mapean columna por columna a la tabla SQL.
UbicaciΓ³n del archivo
Application/Dao/Entities/Tools/Traza.php
CΓ³digo completo
<?php namespace Application\Dao\Entities\Tools; use Intouch\Framework\Annotation\Attributes\Entity; use Intouch\Framework\Annotation\Attributes\EntityField; // El atributo #[Entity] le indica al framework a quΓ© schema de BD pertenece #[Entity(Schema: 'Tools')] class Traza { use TrazaT; // Trait hermano β extensiones manuales van ahΓ // PrimaryKey: true β campo autoincremental, siempre parte en 0 #[EntityField(PrimaryKey: true)] public int $IdTraza = 0; // Campos normales β tipados, con valor por defecto public string $Descripcion = ''; public int $IndVigente = 0; public int $IndEliminado = 0; function __construct() { } }
Anotaciones disponibles
| AnotaciΓ³n | CuΓ‘ndo usarla |
|---|---|
#[Entity(Schema: 'X')] | Siempre. Define el schema de BD (ej: Tools, Core) |
#[EntityField(PrimaryKey: true)] | En el campo ID autoincremental (solo uno por Entity) |
#[EntityField(DataType: 'datetime')] | En campos de fecha/hora (se almacenan como string en PHP) |
Tipos PHP segΓΊn columna SQL
| Tipo SQL Server | Tipo PHP | Valor por defecto |
|---|---|---|
int, tinyint, smallint | int | 0 |
varchar, nvarchar, char | string | '' |
datetime, date | string + #[EntityField(DataType:'datetime')] | '' |
decimal, float | float | 0.0 |
| Columna nullable | ?tipo | null |
DAO Trait de Entity β TrazaT.php
El Trait hermano de la Entity existe para que puedas agregar mΓ©todos o propiedades sin tocar la clase base (que podrΓa regenerarse). Si no necesitas nada extra, lo dejas vacΓo.
UbicaciΓ³n del archivo
Application/Dao/Entities/Tools/TrazaT.php
CΓ³digo β vacΓo (mΓnimo requerido)
<?php namespace Application\Dao\Entities\Tools; trait TrazaT { // Agrega aquΓ mΓ©todos o propiedades extra si los necesitas }
DAO VW Entity β VWTraza.php
La VWEntity representa una vista de base de datos (prefijo VW). Se usa para las bΓΊsquedas y listados, ya que una vista puede tener JOINs y campos calculados que la tabla base no tiene.
Para Traza, que no tiene llaves forΓ‘neas, la vista tiene las mismas columnas que la tabla. Pero el patrΓ³n siempre se respeta.
Tools.Traza tuviera un campo IdProfesion (FK hacia tabla Profesion), la vista VWTraza deberΓa hacer el JOIN y agregar un campo Profesion (sin el prefijo Id) con la descripciΓ³n correspondiente.
UbicaciΓ³n del archivo
Application/Dao/Entities/Tools/VWTraza.php
CΓ³digo completo
<?php namespace Application\Dao\Entities\Tools; use Intouch\Framework\Annotation\Attributes\Entity; use Intouch\Framework\Annotation\Attributes\EntityField; #[Entity(Schema: 'Tools')] class VWTraza { use VWTrazaT; // Nota: la VW NO tiene #[EntityField(PrimaryKey: true)] // porque es una vista de solo lectura public int $IdTraza = 0; public string $Descripcion = ''; public int $IndVigente = 0; public int $IndEliminado = 0; function __construct() { } }
Diferencias entre Entity y VWEntity
| CaracterΓstica | Entity (tabla) | VWEntity (vista) |
|---|---|---|
Tiene PrimaryKey: true | β SΓ | β No |
| Se usa para escritura (Insert/Update/Delete) | β SΓ | β No |
| Se usa para bΓΊsqueda/listado | Puede, pero no es lo ideal | β SΓ |
| Puede tener campos calculados o de JOIN | β No | β SΓ |
Trait de VW Entity β VWTrazaT.php
Application/Dao/Entities/Tools/VWTrazaT.php
<?php namespace Application\Dao\Entities\Tools; trait VWTrazaT { // VacΓo para mantenedor simple }
DAO DAO Service β TrazaDao.php
El DAO es la clase que expone las operaciones de persistencia para una Entity. Hereda de GenericDao, que ya implementa todas las operaciones estΓ‘ndar. En la mayorΓa de los casos, el cuerpo queda prΓ‘cticamente vacΓo: solo el constructor.
UbicaciΓ³n del archivo
Application/Dao/Services/Tools/TrazaDao.php
CΓ³digo completo
<?php namespace Application\Dao\Services\Tools; use Application\Dao\Entities\Tools\Traza; use Intouch\Framework\Dao\GenericDao; class TrazaDao extends GenericDao { use TrazaDaoT; function __construct($domain) { // Le indica a GenericDao quΓ© Entity manejar y a quΓ© conexiΓ³n conectarse parent::__construct( Traza::class, $domain ); } }
ΒΏQuΓ© es $domain?
Es la clave de conexiΓ³n definida en Application/Configuration/connection.config.json. Siempre se pasa desde la capa BLL usando la constante ConnectionEnum::DEFAULT. Nunca pongas el string directamente.
Operaciones que hereda de GenericDao
| MΓ©todo | QuΓ© hace |
|---|---|
Find($id) | Obtiene un registro por su PK |
FindBy($bindings) | Obtiene el primer registro que cumple los filtros |
GetAll() | Obtiene todos los registros de la tabla |
GetBy($bindings) | Obtiene todos los registros que cumplen los filtros |
Insert($entity) | Inserta un registro, retorna la Entity con el nuevo ID |
Update($entity) | Actualiza un registro por su PK |
Delete($id) | Elimina fΓsicamente un registro |
LogicDelete($id) | Marca IndEliminado = 1 (soft delete) |
CountBy($bindings) | Cuenta registros que cumplen los filtros |
Trait del DAO β TrazaDaoT.php
Application/Dao/Services/Tools/TrazaDaoT.php
<?php namespace Application\Dao\Services\Tools; trait TrazaDaoT { // Agrega aquΓ mΓ©todos DAO adicionales si los necesitas // Por ejemplo, consultas SQL personalizadas con stored procedures }
DAO VW DAO Service β VWTrazaDao.php
Igual que el DAO de la tabla, pero apunta a la VWEntity. Se usa exclusivamente para operaciones de lectura (listados y bΓΊsquedas).
UbicaciΓ³n del archivo
Application/Dao/Services/Tools/VWTrazaDao.php
CΓ³digo completo
<?php namespace Application\Dao\Services\Tools; use Application\Dao\Entities\Tools\VWTraza; use Intouch\Framework\Dao\GenericDao; class VWTrazaDao extends GenericDao { use VWTrazaDaoT; function __construct($domain) { parent::__construct( VWTraza::class, // β apunta a la VWEntity $domain ); } }
Trait del VW DAO β VWTrazaDaoT.php
Application/Dao/Services/Tools/VWTrazaDaoT.php
<?php namespace Application\Dao\Services\Tools; trait VWTrazaDaoT { // VacΓo para mantenedor simple }
GenericDao. Tu ΓΊnica responsabilidad es indicarle quΓ© Entity manejar.
RelaciΓ³n entre los archivos DAO
| Archivo | Extiende / Usa | Para quΓ© |
|---|---|---|
Traza.php |
β | Representa la tabla (escritura) |
TrazaDao.php |
GenericDao + Traza |
CRUD sobre la tabla |
VWTraza.php |
β | Representa la vista (lectura) |
VWTrazaDao.php |
GenericDao + VWTraza |
BΓΊsquedas y listados |
BLL DTO β TrazaDto.php
El DTO (Data Transfer Object) es el objeto que viaja entre la capa de PresentaciΓ³n y la capa BLL. Es un objeto plano sin lΓ³gica, con las mismas propiedades que la Entity pero sin las anotaciones del framework DAO.
Usa el constructor con named parameters de PHP 8, lo que permite instanciarlo pasando solo los campos que necesitas.
UbicaciΓ³n del archivo
Application/BLL/DataTransferObjects/Tools/TrazaDto.php
CΓ³digo completo
<?php namespace Application\BLL\DataTransferObjects\Tools; class TrazaDto { use TrazaDtoT; public function __construct( // Mismos campos que la Entity, mismos tipos, mismos valores por defecto public int $IdTraza = 0, public string $Descripcion = '', public int $IndVigente = 0, public int $IndEliminado = 0, ) { } }
Named parameters β ΒΏcΓ³mo se usa?
// Crear un DTO pasando solo los campos necesarios (PHP 8+) $traza = new TrazaDto( IdTraza: 0, Descripcion: 'CardiologΓa', IndVigente: 1, IndEliminado: 0 ); // TambiΓ©n vΓ‘lido: solo ID (el resto toma su valor por defecto) $traza = new TrazaDto(IdTraza: 42);
Trait del DTO β TrazaDtoT.php
Application/BLL/DataTransferObjects/Tools/TrazaDtoT.php
<?php namespace Application\BLL\DataTransferObjects\Tools; trait TrazaDtoT { // El Trait del DTO es el lugar correcto para agregar: // - Propiedades calculadas (ej: nombre completo) // - Colecciones de objetos relacionados (ej: public array $Profesionales = []) // - __get / __set para acceso dinΓ‘mico a propiedades no declaradas }
TrazaDto.php puede regenerarse con bin/gen.sh. Si agregas cΓ³digo ahΓ, lo perderΓ‘s. Todo lo personalizado va en TrazaDtoT.php.
BLL VW DTO β VWTrazaDto.php
Igual que el DTO, pero para la vista. Se usa para los resultados de bΓΊsqueda que llena la grilla.
UbicaciΓ³n del archivo
Application/BLL/DataTransferObjects/Tools/VWTrazaDto.php
CΓ³digo completo
<?php namespace Application\BLL\DataTransferObjects\Tools; class VWTrazaDto { use VWTrazaDtoT; public function __construct( public int $IdTraza = 0, public string $Descripcion = '', public int $IndVigente = 0, public int $IndEliminado = 0, ) { } }
Trait β VWTrazaDtoT.php
<?php namespace Application\BLL\DataTransferObjects\Tools; trait VWTrazaDtoT { // VacΓo para mantenedor simple }
BLL Service β TrazaSvc.php
El Service (Svc) conecta el trΓo DTO + Entity + Dao. Es quien sabe cΓ³mo mapear un DTO a una Entity y viceversa. Hereda de GenericSvc, que ya implementa todo el mapeo y expone las mismas operaciones que el DAO pero trabajando con DTOs.
UbicaciΓ³n del archivo
Application/BLL/Services/Tools/TrazaSvc.php
CΓ³digo completo
<?php namespace Application\BLL\Services\Tools; use Application\Dao\Services\Tools\TrazaDao; use Application\BLL\DataTransferObjects\Tools\TrazaDto; use Application\Dao\Entities\Tools\Traza; use Intouch\Framework\BLL\Service\GenericSvc; class TrazaSvc extends GenericSvc { use TrazaSvcT; function __construct($domain) { parent::__construct( TrazaDto::class, // β el DTO que maneja Traza::class, // β la Entity correspondiente new TrazaDao($domain) // β el DAO que usa internamente ); } }
Operaciones que hereda de GenericSvc
Las mismas que GenericDao pero recibe y retorna DTOs en lugar de Entities:
| MΓ©todo | Entrada | Salida |
|---|---|---|
Find($id) | int (PK) | TrazaDto |
GetBy($bindings) | array de BindVariable | array de TrazaDto |
Insert($dto) | TrazaDto | TrazaDto (con nuevo ID) |
Update($dto) | TrazaDto | void |
Delete($id) | int (PK) | void |
LogicDelete($id) | int (PK) | void (pone IndEliminado = 1) |
CountBy($bindings) | array de BindVariable | int |
Transacciones β mΓ©todos estΓ‘ticos de GenericSvc
// Iniciar transacciΓ³n GenericSvc::BeginMultipleOperations(ConnectionEnum::DEFAULT); // ... operaciones Insert/Update/Delete ... // Confirmar GenericSvc::SaveMultipleOperations(); // O revertir (en el catch) GenericSvc::UndoMultipleOperations();
Trait del Svc β TrazaSvcT.php
Application/BLL/Services/Tools/TrazaSvcT.php
<?php namespace Application\BLL\Services\Tools; trait TrazaSvcT { // AquΓ puedes definir $innerMappings = [] para mapeos especiales DTO β Entity // VacΓo para mantenedor simple }
BLL VW Service β VWTrazaSvc.php
El Svc de la vista, que se usa exclusivamente para bΓΊsquedas con filtros en el BO.
UbicaciΓ³n del archivo
Application/BLL/Services/Tools/VWTrazaSvc.php
CΓ³digo completo
<?php namespace Application\BLL\Services\Tools; use Application\Dao\Services\Tools\VWTrazaDao; use Application\BLL\DataTransferObjects\Tools\VWTrazaDto; use Application\Dao\Entities\Tools\VWTraza; use Intouch\Framework\BLL\Service\GenericSvc; class VWTrazaSvc extends GenericSvc { use VWTrazaSvcT; function __construct($domain) { parent::__construct( VWTrazaDto::class, VWTraza::class, new VWTrazaDao($domain) ); } }
Trait β VWTrazaSvcT.php
<?php namespace Application\BLL\Services\Tools; trait VWTrazaSvcT { // VacΓo para mantenedor simple }
BLL Business Object β TrazaBO.php
El Business Object (BO) es el corazΓ³n de la lΓ³gica de negocio. No hereda de ninguna clase base del framework. Contiene un mΓ©todo por cada caso de uso del mantenedor: guardar, buscar, eliminar, validar.
El Controller instancia el BO y le delega todo. El BO nunca genera HTML ni conoce controles de UI.
UbicaciΓ³n del archivo
Application/BLL/BusinessObjects/Mantenedores/Tools/TrazaBO.php
CΓ³digo completo
<?php namespace Application\BLL\BusinessObjects\Mantenedores\Tools; use Application\BLL\Services\Tools\TrazaSvc; use Application\BLL\Services\Tools\VWTrazaSvc; use Application\BLL\DataTransferObjects\Tools\TrazaDto; use Application\Configuration\ConnectionEnum; use Intouch\Framework\BLL\Service\GenericSvc; use Intouch\Framework\Dao\BindVariable; use Intouch\Framework\Exceptions\BusinessException; use Intouch\Framework\Exceptions\ExceptionCodesEnum; use ReflectionClass; class TrazaBO { use TrazaBOT; // β flags y validaciones en el Trait // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // GUARDAR (actualizar registro existente) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function GuardarTraza($registro) { $trazaSvc = new TrazaSvc(ConnectionEnum::DEFAULT); try { GenericSvc::BeginMultipleOperations(ConnectionEnum::DEFAULT); $traza = new TrazaDto( IdTraza: $registro->IdTraza, Descripcion: $registro->Descripcion, IndVigente: $registro->IndVigente, IndEliminado: 0 // nunca se actualiza por esta vΓa ); $trazaSvc->Update($traza); GenericSvc::SaveMultipleOperations(); return true; } catch(\Exception $e) { GenericSvc::UndoMultipleOperations(); throw $e; } } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // GUARDAR NUEVO (insertar registro) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function GuardarNuevaTraza($registro) { $trazaSvc = new TrazaSvc(ConnectionEnum::DEFAULT); try { GenericSvc::BeginMultipleOperations(ConnectionEnum::DEFAULT); // Garantizar que se crea un registro nuevo (PK = 0) $registro->IdTraza = 0; $traza = new TrazaDto( IdTraza: $registro->IdTraza, Descripcion: $registro->Descripcion, IndVigente: 1, // los nuevos registros parten vigentes IndEliminado: 0 ); $nuevaTraza = $trazaSvc->Insert($traza); // Verificar que el insert fue exitoso if (!isset($nuevaTraza) || $nuevaTraza->IdTraza <= 0) { GenericSvc::UndoMultipleOperations(); throw new \Exception("Ha ocurrido un problema al guardar el registro", 1); } GenericSvc::SaveMultipleOperations(); return true; } catch(\Exception $e) { GenericSvc::UndoMultipleOperations(); throw $e; } } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // BUSCAR (listado con filtros) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function BuscarTraza($filtros) { // Usamos VWTrazaSvc para la bΓΊsqueda (vista de BD) $trazaSvc = new VWTrazaSvc(ConnectionEnum::DEFAULT); $bindings = []; // Flag del Trait: ocultar eliminados lΓ³gicamente if ($this->OcultarEliminadosEnBusqueda) { $bindings[] = new BindVariable("IndEliminado", "=", 0); } // Filtro por ID exacto if (isset($filtros->IdTraza) && $filtros->IdTraza != "-1") { $bindings[] = new BindVariable("IdTraza", "=", $filtros->IdTraza); } // Filtro por descripciΓ³n (bΓΊsqueda parcial) if (isset($filtros->Descripcion) && $filtros->Descripcion != "") { $bindings[] = new BindVariable("Descripcion", "LIKE", '%' . $filtros->Descripcion . '%'); } return $trazaSvc->GetBy( bindings: $bindings, avoidNull: true // evita nulls en el resultado ); } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // VALIDAR ELIMINACIΓN (retorna mensaje si no se puede eliminar) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function SePuedeEliminarTraza($IdTraza) { $mensajes = $this->ValidarEliminarTraza($IdTraza); if (count($mensajes) > 0) { return implode('<br>', $mensajes); } return ""; // string vacΓo = se puede eliminar sin advertencias } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // ELIMINAR // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function EliminarTraza($IdTraza) { GenericSvc::BeginMultipleOperations(ConnectionEnum::DEFAULT); try { // Eliminar dependencias en cascada (definidas en el Trait) $this->EliminarElementosDependientesTraza($IdTraza); $trazaSvc = new TrazaSvc(ConnectionEnum::DEFAULT); // Flag del Trait: eliminaciΓ³n lΓ³gica (IndEliminado=1) o fΓsica if ($this->EliminacionLogica) { $trazaSvc->LogicDelete($IdTraza); // soft delete } else { $trazaSvc->Delete($IdTraza); // hard delete } GenericSvc::SaveMultipleOperations(); } catch (\Exception $e) { GenericSvc::UndoMultipleOperations(); throw new BusinessException( ExceptionCodesEnum::ERR_DATA_DELETE, 'al intentar eliminar el registro de Traza' ); } return ''; } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // OBTENER DATA (para cargar el popup nuevo o ediciΓ³n) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ public function ObtenerDataTraza( $IdTraza = 0, $NombreIdPadre = '', $ValorIdPadre = -1 ) { $data = []; if ($IdTraza != 0) { // EdiciΓ³n: cargar el registro existente $trazaSvc = new TrazaSvc(ConnectionEnum::DEFAULT); $data['Elemento'] = $trazaSvc->Find($IdTraza); } else { // Nuevo: crear un DTO vacΓo $data['Elemento'] = new TrazaDto(IdTraza: 0); } return (object)$data; } }
BindVariable β filtros dinΓ‘micos
El objeto BindVariable es la forma correcta de pasar filtros a GetBy, FindBy y CountBy:
| Operador | Ejemplo |
|---|---|
= | new BindVariable("IndEliminado", "=", 0) |
LIKE | new BindVariable("Descripcion", "LIKE", '%cardio%') |
>, <, >=, <=, <> | new BindVariable("IdTraza", ">", 10) |
IN | new BindVariable("IdTraza", "IN", [1,2,3]) |
BLL Trait del BO β TrazaBOT.php
El Trait del BO contiene los flags de comportamiento y los mΓ©todos de validaciΓ³n y eliminaciΓ³n en cascada. Es el lugar donde personalizas el comportamiento del mantenedor sin tocar la clase base.
UbicaciΓ³n del archivo
Application/BLL/BusinessObjects/Mantenedores/Tools/TrazaBOT.php
CΓ³digo completo
<?php namespace Application\BLL\BusinessObjects\Mantenedores\Tools; use Application\Configuration\ConnectionEnum; use Intouch\Framework\Dao\BindVariable; trait TrazaBOT { // ββ FLAGS DE COMPORTAMIENTO ββββββββββββββββββββββββββββββββββ // true β EliminarTraza usarΓ‘ LogicDelete (IndEliminado=1) // false β usarΓ‘ Delete (borrado fΓsico) public $EliminacionLogica = true; // true β BuscarTraza filtrarΓ‘ WHERE IndEliminado = 0 public $OcultarEliminadosEnBusqueda = true; // ββ VALIDACIONES DE ELIMINACIΓN ββββββββββββββββββββββββββββββ public function ValidarEliminarTraza($IdTraza) : array { $mensajes = []; // Ejemplo: verificar si hay Profesionales asociados antes de eliminar // // $profesionalSvc = new ProfesionalSvc(ConnectionEnum::DEFAULT); // $cantidad = $profesionalSvc->CountBy([ // new BindVariable('IndEliminado', '=', 0), // new BindVariable('IdTraza', '=', $IdTraza) // ]); // // if ($cantidad > 0) { // $mensajes[] = "Existen $cantidad profesional(es) asociados a esta traza."; // } return $mensajes; } // ββ ELIMINACIΓN EN CASCADA βββββββββββββββββββββββββββββββββββ public function EliminarElementosDependientesTraza($IdTraza) { // Ejemplo: eliminar los profesionales de esta traza antes de eliminarla // (new ProfesionalBO())->EliminarByTraza($IdTraza); return; } }
ΒΏPor quΓ© separar el Trait del BO?
| TrazaBO.php | TrazaBOT.php |
|---|---|
| Estructura fija: los 5 mΓ©todos del mantenedor (Guardar, GuardarNuevo, Buscar, Eliminar, ObtenerData) | Personalizaciones: flags, validaciones antes de eliminar, eliminaciΓ³n en cascada |
| PodrΓa regenerarse | Siempre se edita manualmente, nunca se sobreescribe |
IndEliminado o cuando realmente necesitas borrar el registro de la base de datos. Para la mayorΓa de los mantenedores del sistema, la eliminaciΓ³n lΓ³gica es la opciΓ³n correcta.
CTRL Controller Web β TrazaController.php
El Controller Web tiene una sola responsabilidad: renderizar la vista principal cuando el usuario navega a la URL del mantenedor. Es el punto de entrada de la ruta web (no AJAX).
En un mantenedor simple, este controller tiene un ΓΊnico mΓ©todo: Ver().
UbicaciΓ³n del archivo
Application/Controllers/Mantenedores/Tools/TrazaController.php
CΓ³digo completo
<?php namespace Application\Controllers\Mantenedores\Tools; use Application\BLL\BusinessObjects\Mantenedores\Tools\TrazaBO; use Application\Resources\AssetManagerFactory; use Intouch\Framework\Annotation\Attributes\ReturnViewResult; use Intouch\Framework\Annotation\Attributes\Route; use Intouch\Framework\Controllers\BaseController; // Seguridad por defecto en toda la clase #[Route(Authorization: 'ALLOW_ALL', RequireSession: true)] class TrazaController extends BaseController { use TrazaControllerT; public function __construct() { parent::__construct(assetManagerFactory: new AssetManagerFactory()); } // Solo ADMIN puede acceder a este mantenedor #[Route(Authorization: 'ENFORCE', Roles: ['ADMIN'], RequireSession: true)] #[ReturnViewResult] public function Ver() { // Preparar datos iniciales para la vista (DTO vacΓo, listas de selects, etc.) $trazaBO = new TrazaBO(); $data = $trazaBO->ObtenerDataTraza(); // RenderView busca la vista en: Views/Mantenedores/Tools/Traza/verView.php return $this->RenderView('ver', $data); } }
Anotaciones de ruta y seguridad
| AnotaciΓ³n | Significado |
|---|---|
#[Route(Authorization: 'ALLOW_ALL')] | Por defecto permite acceso (se refina en cada mΓ©todo) |
#[Route(Authorization: 'ENFORCE', Roles: ['ADMIN'])] | Solo usuarios con el rol ADMIN pueden ejecutar este mΓ©todo |
#[Route(RequireSession: true)] | Requiere sesiΓ³n iniciada; redirige al login si no la hay |
#[ReturnViewResult] | Le indica al framework que este mΓ©todo retorna HTML renderizado |
ΒΏCΓ³mo resuelve RenderView la vista?
El mΓ©todo RenderView('ver', $data) busca el archivo:
Application/Views/[namespace del controller]/Traza/verView.php
Como el namespace es Mantenedores/Tools, buscarΓ‘:
Application/Views/Mantenedores/Tools/Traza/verView.php
Trait del Controller Web β TrazaControllerT.php
Application/Controllers/Mantenedores/Tools/TrazaControllerT.php
<?php namespace Application\Controllers\Mantenedores\Tools; trait TrazaControllerT { // MΓ©todos adicionales del controller web }
CTRL Controller API β Api/TrazaController.php
El Controller API expone los endpoints AJAX que el JavaScript del frontend llama. Cada mΓ©todo corresponde a una acciΓ³n del mantenedor: buscar, guardar, eliminar, abrir popup.
Todos los mΓ©todos estΓ‘n decorados con #[ReturnActionResult] o #[ReturnActionViewResult] segΓΊn si retornan datos o HTML.
UbicaciΓ³n del archivo
Application/Controllers/Api/Mantenedores/Tools/TrazaController.php
CΓ³digo completo
<?php namespace Application\Controllers\Api\Mantenedores\Tools; use Application\BLL\BusinessObjects\Mantenedores\Tools\TrazaBO; use Application\Resources\AssetManagerFactory; use Intouch\Framework\Annotation\Attributes\ReturnActionResult; use Intouch\Framework\Annotation\Attributes\ReturnActionViewResult; use Intouch\Framework\Annotation\Attributes\Route; use Intouch\Framework\Controllers\BaseController; use Intouch\Framework\Exceptions\BusinessException; use Intouch\Framework\Exceptions\ExceptionCodesEnum; use Intouch\Framework\View\Display; #[Route(Authorization: 'ALLOW_ALL', RequireSession: false)] class TrazaController extends BaseController { use TrazaControllerT; public function __construct() { parent::__construct(assetManagerFactory: new AssetManagerFactory()); } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Verificar si se puede eliminar (retorna advertencias) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ #[Route(Methods: ['POST'], Authorization: 'ALLOW_ALL', RequireSession: false)] #[ReturnActionResult] public function SePuedeEliminarTraza($IdTraza) { $trazaBO = new TrazaBO(); return $trazaBO->SePuedeEliminarTraza($IdTraza); } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Eliminar el registro // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ #[Route(Methods: ['POST'], Authorization: 'ALLOW_ALL', RequireSession: false)] #[ReturnActionResult] public function EliminarTraza($IdTraza) { $trazaBO = new TrazaBO(); return $trazaBO->EliminarTraza($IdTraza); } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Abrir el popup (nuevo o ediciΓ³n) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ #[Route(Methods: ['POST'], Authorization: 'ALLOW_ALL', RequireSession: false)] #[ReturnActionViewResult] public function PopUpTraza($IdTraza, $NombreIdPadre, $ValorIdPadre) { $trazaBO = new TrazaBO(); $data = $trazaBO->ObtenerDataTraza( $IdTraza, $NombreIdPadre, $ValorIdPadre ); // RenderView β Views/Mantenedores/Tools/Traza/popupView.php $view = Display::GetRenderer('Mantenedores/Tools/Traza') ->RenderView('popup', $data); return $view; } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Guardar (nuevo o existente, diferenciado por ID) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ #[Route(Methods: ['POST'])] #[ReturnActionResult] public function GuardarTraza($TrazaRegistro) { $trazaBO = new TrazaBO(); try { // ID = 0 β es nuevo; ID > 0 β actualizar existente if (!isset($TrazaRegistro->IdTraza) || $TrazaRegistro->IdTraza == 0) { $trazaBO->GuardarNuevaTraza($TrazaRegistro); } else { $trazaBO->GuardarTraza($TrazaRegistro); } } catch (\Exception $ex) { throw new BusinessException( ExceptionCodesEnum::ERR_DATA_INSERT, ' al guardar el registro' ); } return 0; } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Buscar (retorna HTML de la grilla) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ #[Route(Methods: ['POST'], Authorization: 'ALLOW_ALL', RequireSession: false)] #[ReturnActionViewResult] public function BuscarTraza($RegistroTrazaBuscar) { $trazaBO = new TrazaBO(); $trazas = $trazaBO->BuscarTraza($RegistroTrazaBuscar); // RenderView β Views/Mantenedores/Tools/Traza/verGridView.php $view = Display::GetRenderer('Mantenedores/Tools/Traza') ->RenderView('verGrid', $trazas); return $view; } }
Diferencia entre las anotaciones de retorno
| AnotaciΓ³n | QuΓ© retorna | CuΓ‘ndo usarla |
|---|---|---|
#[ReturnActionResult] |
JSON con datos primitivos (string, int, bool) | Guardar, Eliminar, verificaciones |
#[ReturnActionViewResult] |
JSON con HTML renderizado | Buscar (grilla), PopUp (formulario modal) |
#[ReturnViewResult] |
HTML completo (pΓ‘gina entera) | Solo en el Controller Web (no API) |
Trait del Controller API β TrazaControllerT.php
Application/Controllers/Api/Mantenedores/Tools/TrazaControllerT.php
<?php namespace Application\Controllers\Api\Mantenedores\Tools; trait TrazaControllerT { // Endpoints adicionales personalizados van aquΓ }
VIEW Vista Principal β verView.php
Esta es la pantalla principal del mantenedor. Contiene el formulario de filtros de bΓΊsqueda, los botones de buscar/limpiar/nuevo, y un contenedor vacΓo donde se inyectarΓ‘ la grilla de resultados vΓa AJAX.
No contiene HTML crudo: usa los componentes del framework (Display, Panel, Container, FormRowGroup, etc.).
UbicaciΓ³n del archivo
Application/Views/Mantenedores/Tools/Traza/verView.php
Directivas de layout y bundle JS
@@Layout(authenticated) β usa el layout de app autenticada @@IncludeScriptBundle(mantenedorTrazaJS) β incluye el JS del mantenedor
CΓ³digo completo
<?php use Application\Views\Mantenedores\Tools\Traza\verGridT; use Intouch\Framework\View\Display; use Intouch\Framework\View\DisplayDefinitions\Button; use Intouch\Framework\View\DisplayDefinitions\FormButton; use Intouch\Framework\View\DisplayDefinitions\FormRowFieldText; use Intouch\Framework\View\DisplayDefinitions\FormRowGroup; use Intouch\Framework\View\DisplayEvents\ButtonOnClickEvent; use Intouch\Framework\View\DisplayEvents\FormButtonOnClickEvent; use Intouch\Framework\Widget\Container; use Intouch\Framework\Widget\Definitions\ActionButton\ButtonStyleEnum; use Intouch\Framework\Widget\Panel; use Intouch\Framework\Widget\Text; use Intouch\Framework\Widget\TitleDescription; ?> @@Layout(authenticated) @@IncludeScriptBundle(mantenedorTrazaJS) <?php // ββ ENCABEZADO DE PΓGINA βββββββββββββββββββββββββββββββββββββββββ $pageHeader = verGridT::GetHeader(); // tΓtulo e Γcono definidos en verGridT $pageHeader->Draw(); $display = new Display(); // ββ BOTONES DEL FORMULARIO DE BΓSQUEDA ββββββββββββββββββββββββββ $display->AddButton( new FormButton( Key: 'btnBuscarTraza', FormKey: 'frmencabezado', Child: new Text('Buscar Trazas'), ButtonStyle: ButtonStyleEnum::BUTTON_INFO, Classes: ['pull-right'], Events: [new FormButtonOnClickEvent()] ) ); $display->AddButton( new FormButton( Key: 'btnLimpiarTraza', FormKey: 'frmencabezado', Child: new Text('Limpiar Filtros'), ButtonStyle: ButtonStyleEnum::BUTTON_DEFAULT, Styles: [['margin-right', '8px']], Classes: ['pull-right'], Events: [new FormButtonOnClickEvent()] ) ); // ββ CAMPOS DE FILTRO βββββββββββββββββββββββββββββββββββββββββββββ $rowGroups = []; $rowGroups[] = new FormRowGroup( Key: 'grp-informacion-traza', Title: 'Opciones de BΓΊsqueda', Rows: [ [ // Campo de texto para filtrar por descripciΓ³n 'Descripcion' => new FormRowFieldText( PropertyName: 'Descripcion', Label: 'Nombre', Colspan: 4 ), ] ] ); $display->AddFormFromObject( formKey: 'frmencabezado', object: $data, // DTO vacΓo pasado desde el Controller fillData: false, // no pre-llenar con datos keyFieldName: 'IdTraza', rowGroups: $rowGroups ); // ββ BOTΓN NUEVO ββββββββββββββββββββββββββββββββββββββββββββββββββ $display->AddButton( new Button( Key: 'btnNuevaTraza', Child: new Text('Nuevo'), Classes: ['pull-right'], ButtonStyle: ButtonStyleEnum::BUTTON_PRIMARY, Events: [new ButtonOnClickEvent()] ) ); // ββ LAYOUT GENERAL βββββββββββββββββββββββββββββββββββββββββββββββ ( new Container( Classes: ['view-content'], Children: [ // Panel superior: filtros de bΓΊsqueda new Panel( Body: new Container( Children: [ $display->Widgets()['frmencabezado'], $display->Widgets()['btnBuscarTraza'], $display->Widgets()['btnLimpiarTraza'], ] ), ), // Panel inferior: contenedor de la grilla (se llena por AJAX) new Panel( Header: new Container( Children: [ $display->Widgets()['btnNuevaTraza'], new TitleDescription( Title: 'Resultado', Description: 'Listado de: Traza' ) ] ), Body: new Container( Key: 'formulario-traza', β ID usado por el JS para inyectar la grilla Children: [] ), ) ] ) )->Draw(); $display->DrawScripts();
Componentes clave
| Componente | QuΓ© genera |
|---|---|
FormButton | BotΓ³n que envΓa un formulario; su evento OnClick llama a btnBuscarTraza_OnClick() en el JS |
Button | BotΓ³n simple; su evento llama a btnNuevaTraza_OnClick() |
FormRowFieldText | Campo de texto en el formulario |
Container(Key: 'formulario-traza') | El <div> donde el JS inyectarΓ‘ la grilla con $('#formulario-traza').html(result) |
Key de cada widget (botΓ³n, formulario, etc.) se convierte en el id HTML del elemento. El JavaScript usa estos IDs para disparar eventos y actualizar el DOM.
VIEW Vista Grilla β verGridView.php
Esta vista genera la tabla de resultados. El Controller API la renderiza y la devuelve como HTML al JavaScript, que la inyecta en el contenedor #formulario-traza.
Define las columnas de la tabla, los botones de acciΓ³n por fila (Editar, Eliminar), y delega el orden y visibilidad a verGridT.
UbicaciΓ³n del archivo
Application/Views/Mantenedores/Tools/Traza/verGridView.php
CΓ³digo completo
<?php use Application\Views\Mantenedores\Tools\Traza\verGridT; use Intouch\Framework\BLL\Filters\DataTableSettingsFilterDto; use Intouch\Framework\View\Display; use Intouch\Framework\View\DisplayDefinitions\TableButton; use Intouch\Framework\View\DisplayDefinitions\TogglePlacementEnum; use Intouch\Framework\View\DisplayDefinitions\TableCell; use Intouch\Framework\View\DisplayEvents\TableButtonOnClickEvent; use Intouch\Framework\Widget\Container; use Intouch\Framework\Widget\Definitions\ActionButton\ButtonStyleEnum; use Intouch\Framework\Widget\FaIcon; use Intouch\Framework\Widget\Label; use Intouch\Framework\Widget\Definitions\Label\LabelSizeEnum; use Intouch\Framework\Widget\Definitions\Label\LabelStyleEnum; $display = new Display(); // ββ DEFINICIΓN DE COLUMNAS βββββββββββββββββββββββββββββββββββββββ $cells = [ // Columna Descripcion β texto plano 'Descripcion' => new TableCell( PropertyName: "Descripcion", Label: "Nombre", ), // Columna IndVigente β renderizada como etiqueta SI/NO 'IndVigente' => new TableCell( PropertyName: "IndVigente", Label: "ΒΏVigente?", BodyClasses: ['text-center'], FormatFunction: function($element, $cell) { return new Label( LabelStyle: ($element->IndVigente == 1) ? LabelStyleEnum::LABEL_SUCCESS : LabelStyleEnum::LABEL_LIGHT, LabelSize: LabelSizeEnum::LABEL_LARGE, Content: ($element->IndVigente == 1) ? 'SI' : 'NO' ); } ), ]; // Aplicar orden y visibilidad de columnas definido en verGridT $cells = verGridT::GetCellOrderAndVisibility($cells); foreach ($cells as $cell) { verGridT::CustomCell($cell); } // ββ BOTONES DE ACCIΓN POR FILA βββββββββββββββββββββββββββββββββββ $buttons = verGridT::GetCustomButtons(); // BotΓ³n Editar β llama a btnEditarTraza_OnClick() en el JS $buttons[] = new TableButton( Child: new FaIcon('fa-edit'), Key: 'btnEditarTraza', OnClickClass: 'btn-editar-traza', ButtonStyle: ButtonStyleEnum::BUTTON_WARNING, TogglePopUp: true, ToggleText: 'Editar Traza', TogglePlacement: TogglePlacementEnum::TOP, Events: [new TableButtonOnClickEvent()], ); // BotΓ³n Eliminar β llama a btnEliminarTraza_OnClick() en el JS $buttons[] = new TableButton( Key: 'btnEliminarTraza', Child: new FaIcon('fa-trash'), OnClickClass: 'btn-eliminar-traza', ButtonStyle: ButtonStyleEnum::BUTTON_DANGER, TogglePopUp: true, ToggleText: 'Eliminar Traza', TogglePlacement: TogglePlacementEnum::TOP, Events: [new TableButtonOnClickEvent()], ); // ββ TABLA DE DATOS βββββββββββββββββββββββββββββββββββββββββββββββ $display->AddTableFromCollection( tableKey: 'tbTraza', RowIdFieldName: 'IdTraza', β campo que actΓΊa como PK de la fila RowAttributeNames: [ β atributos data-xxx que se agregan a cada fila 'IdTraza', 'Descripcion', 'IndVigente', 'IndEliminado' ], Data: $data, β array de VWTrazaDto Buttons: $buttons, CellDefinitions: $cells, TablaSimple: false, CustomDataTable: new DataTableSettingsFilterDto( HideAllButtons: true ) ); (new Container( Children: [$display->Widgets()['tbTraza']] ))->Draw(); $display->DrawScripts(addLoadEvent: false);
ΒΏCΓ³mo funciona RowAttributeNames?
El framework agrega atributos data-xxx a cada fila <tr> de la tabla. Cuando el usuario hace click en "Editar" o "Eliminar", el JS lee esos atributos para saber quΓ© registro manipular:
// En el JS, el botΓ³n recibe rowInfo con los datos de la fila function btnEditarTraza_OnClick(rowInfo) { PopUpTraza(rowInfo.RowData.pk); // pk = IdTraza }
FormatFunction β personalizar celdas
Cuando necesitas mostrar algo diferente al valor crudo del campo, usas FormatFunction:
| Caso | Ejemplo |
|---|---|
| Etiqueta SI/NO | Retornar new Label(...) |
| Moneda | Retornar _mon($element->Precio) |
| Fecha formateada | Retornar (new DateTime($element->Fecha))->format('d-m-Y') |
| TΓtulo + descripciΓ³n | Retornar new TitleDescription(...) |
VIEW Vista Popup β popupView.php
Esta vista genera el formulario modal que aparece cuando el usuario hace click en "Nuevo" o "Editar". El mismo archivo sirve para ambos casos: detecta el modo segΓΊn si IdTraza > 0.
UbicaciΓ³n del archivo
Application/Views/Mantenedores/Tools/Traza/popupView.php
CΓ³digo completo
<?php use Application\Views\Mantenedores\Tools\Traza\verGridT; use Intouch\Framework\View\Display; use Intouch\Framework\View\DisplayDefinitions\FormButton; use Intouch\Framework\View\DisplayDefinitions\FormRowFieldCheck; use Intouch\Framework\View\DisplayDefinitions\FormRowFieldHidden; use Intouch\Framework\View\DisplayDefinitions\FormRowFieldText; use Intouch\Framework\View\DisplayDefinitions\FormRowGroup; use Intouch\Framework\View\DisplayEvents\FormButtonOnClickEvent; use Intouch\Framework\Widget\Container; use Intouch\Framework\Widget\Definitions\ActionButton\ButtonStyleEnum; use Intouch\Framework\Widget\Definitions\PopUpModal\ColorLineStyleEnum; use Intouch\Framework\Widget\PopUpContent; use Intouch\Framework\Widget\Text; $display = new Display(); // ββ DETECTAR MODO (nuevo vs ediciΓ³n) ββββββββββββββββββββββββββββ $editMode = (isset($data) && isset($data->Elemento) && ($data->Elemento->IdTraza) > 0); // Adaptar textos y estilos segΓΊn el modo $btnGuardarText = ($editMode) ? 'Guardar Cambios' : 'Guardar Nuevo'; $btnStyle = ($editMode) ? ButtonStyleEnum::BUTTON_WARNING : ButtonStyleEnum::BUTTON_PRIMARY; $lineStyle = ($editMode) ? ColorLineStyleEnum::LINE_WARNING : ColorLineStyleEnum::LINE_SUCCESS; $popupTitle = ($editMode) ? 'Editar' : 'Nuevo'; $popupSubTitle = ($editMode) ? 'Editar Traza' : 'Nueva Traza'; // ββ BOTΓN GUARDAR ββββββββββββββββββββββββββββββββββββββββββββββββ $display->AddButton( new FormButton( Key: 'btnGuardarTraza', FormKey: 'frmTraza', Child: new Text($btnGuardarText), ButtonStyle: $btnStyle, Events: [new FormButtonOnClickEvent()] ) ); // ββ CAMPOS DEL FORMULARIO ββββββββββββββββββββββββββββββββββββββββ $frmGroups = []; $frmGroups[] = new FormRowGroup( Key: 'IdTraza', Rows: [ [ // ID siempre oculto (lo lee el JS para saber si es insert o update) 'IdTraza' => new FormRowFieldHidden( PropertyName: 'IdTraza' ), // DescripciΓ³n β campo de texto visible 'Descripcion' => new FormRowFieldText( PropertyName: 'Descripcion', Label: 'DescripciΓ³n', Required: false, Colspan: 4 ), // IndVigente: // en EDICIΓN β checkbox visible para poder cambiar vigencia // en NUEVO β hidden (el BO lo setea en 1 automΓ‘ticamente) 'IndVigente' => (isset($data->Elemento) && ($data->Elemento->IdTraza > 0)) ? new FormRowFieldCheck( PropertyName: 'IndVigente', Label: '<span> </span>', Title: 'Vigente', Required: false, Colspan: 4 ) : new FormRowFieldHidden( PropertyName: 'IndVigente' ), // IndEliminado β siempre hidden (nunca editable desde UI) 'IndEliminado' => new FormRowFieldHidden( PropertyName: 'IndEliminado' ), ] ] ); // Delegar el orden de campos a verGridT $frmGroups = verGridT::GetFormOrderAndVisibility($frmGroups[0], $data); // ββ FORMULARIO βββββββββββββββββββββββββββββββββββββββββββββββββββ $display->AddFormFromObject( formKey: 'frmTraza', keyFieldName: 'IdTraza', object: $data->Elemento, β pre-llena con el DTO (vacΓo en nuevo, con datos en ediciΓ³n) fillData: true, rowGroups: $frmGroups ); // ββ CONTENEDOR MODAL βββββββββββββββββββββββββββββββββββββββββββββ $popUp = new PopUpContent( Key: 'popupTraza', ColorLineStyle: $lineStyle, Title: $popupTitle, SubTitle: $popupSubTitle, DismissButtonText: 'Cancelar', DismissButtonStyle: ButtonStyleEnum::BUTTON_DEFAULT, Buttons: [ $display->Widgets()['btnGuardarTraza'], ], Content: new Container( Classes: ['view-content'], Children: [$display->Widgets()['frmTraza']] ) ); $popUp->Draw(); $display->DrawScripts(addLoadEvent: false); ?> <script> $(function() { // Quitar marca de error al volver a editar el campo $('.form-input').on('focus', function() { $(this).removeClass('form-error'); }); $('.form-input').on('select2:select', function() { $(this).removeClass('form-error'); }); }); //# sourceURL=Traza_PopUp_OnPageLoad.js </script>
Tipos de campos disponibles
| Clase | Genera | CuΓ‘ndo usar |
|---|---|---|
FormRowFieldText | Input tipo texto | Strings cortos |
FormRowFieldHidden | Input hidden | IDs, flags que no debe ver el usuario |
FormRowFieldCheck | Checkbox | Campos booleanos (IndVigente, etc.) |
FormRowFieldDate | Date picker | Fechas |
FormRowFieldSelect | Select2 (dropdown) | Llaves forΓ‘neas (FK) |
FormRowFieldContent | HTML personalizado | Contenido libre |
ColorLineStyle del PopUp
| Constante | Color | CuΓ‘ndo usar |
|---|---|---|
LINE_SUCCESS | Verde | CreaciΓ³n de nuevo registro |
LINE_WARNING | Naranja/Amarillo | EdiciΓ³n de registro existente |
LINE_DANGER | Rojo | Operaciones destructivas |
LINE_INFO | Azul | VisualizaciΓ³n informativa |
VIEW Template de Grilla β verGridT.php
El verGridT es una clase estΓ‘tica que actΓΊa como "template de configuraciΓ³n" de la grilla. Define el orden de columnas, el orden de campos del formulario, y permite personalizar celdas y agregar botones extra. Es el archivo mΓ‘s frecuentemente modificado cuando se necesita ajustar el mantenedor.
UbicaciΓ³n del archivo
Application/Views/Mantenedores/Tools/Traza/verGridT.php
CΓ³digo completo
<?php namespace Application\Views\Mantenedores\Tools\Traza; use Intouch\Framework\View\DisplayDefinitions\FormRowGroup; use Intouch\Framework\View\DisplayDefinitions\TableButton; use Intouch\Framework\View\DisplayEvents\TableButtonOnClickEvent; use Intouch\Framework\View\DisplayDefinitions\TogglePlacementEnum; use Intouch\Framework\Widget\Definitions\ActionButton\ButtonStyleEnum; use Intouch\Framework\Widget\FaIcon; use Intouch\Framework\Widget\PageHeader; class verGridT { // ββ ORDEN DE COLUMNAS DE LA GRILLA ββββββββββββββββββββββββββββββ // Solo las columnas que retornes aquΓ aparecerΓ‘n en la tabla. // Cambia el orden agregando/quitando elementos del $newArray. public static function GetCellOrderAndVisibility($cells, $data = null) : array { $newArray = []; $newArray[] = $cells['Descripcion']; $newArray[] = $cells['IndVigente']; return $newArray; } // ββ ORDEN DE CAMPOS DEL FORMULARIO POPUP ββββββββββββββββββββββββ // Define quΓ© campos aparecen y en quΓ© orden en el modal. public static function GetFormOrderAndVisibility($frmGroup, $data) : array { $result = []; $newfrmGroup = new FormRowGroup(Key: 'frmgrp-traza', Rows: []); $row = []; $row[] = $frmGroup->Rows[0]['IdTraza']; $row[] = $frmGroup->Rows[0]['Descripcion']; $row[] = $frmGroup->Rows[0]['IndVigente']; $row[] = $frmGroup->Rows[0]['IndEliminado']; $newfrmGroup->Rows[] = $row; $result[] = $newfrmGroup; return $result; } // ββ ORDEN DE CAMPOS DE BΓSQUEDA βββββββββββββββββββββββββββββββββ public static function GetSearchFormOrderAndVisibility($rowGroup) : array { $result = []; $newrowGroup = new FormRowGroup(Key: 'rowgrp-traza', Rows: []); $row = []; $row[] = $rowGroup->Rows[0]['Descripcion']; $newrowGroup->Rows[] = $row; $result[] = $newrowGroup; return $result; } // ββ PERSONALIZAR CELDAS INDIVIDUALMENTE βββββββββββββββββββββββββ // Modifica propiedades de una celda especΓfica. // switch por PropertyName para afectar solo esa columna. public static function CustomCell($cell) { // Ejemplo automΓ‘tico: columnas "IndXxx" muestran label "ΒΏXxx?" if (substr($cell->PropertyName, 0, 3) == 'Ind') { $cell->Label = 'ΒΏ' . substr($cell->PropertyName, 3) . '?'; } // PersonalizaciΓ³n por columna especΓfica switch ($cell->PropertyName) { // Ejemplo: // case "Descripcion": // $cell->BodyClasses = ['text-bold']; // return; default: return; } } // ββ BOTONES EXTRA POR FILA ββββββββββββββββββββββββββββββββββββββ // Agrega botones adicionales (ademΓ‘s de Editar y Eliminar). // Ej: un botΓ³n "Ver Profesionales" que abre un sub-mantenedor. public static function GetCustomButtons($buttons = [], $filtros = null, $isPopup = false) { // Ejemplo comentado: // $buttons[] = new TableButton( // Child: new FaIcon('fa-users'), // Key: 'btnVerProfesionales', // OnClickClass: 'btn-ver-profesionales', // ButtonStyle: ButtonStyleEnum::BUTTON_PRIMARY, // TogglePopUp: true, // ToggleText: 'Ver Profesionales', // TogglePlacement: TogglePlacementEnum::TOP, // Events: [new TableButtonOnClickEvent()], // ); return $buttons; } // ββ ENCABEZADO DE PΓGINA ββββββββββββββββββββββββββββββββββββββββ // TΓtulo, descripciΓ³n e Γcono FontAwesome de la pantalla. public static function GetHeader() { return new PageHeader( Title: 'Trazas', Description: 'Seleccione un elemento para editar, o agregue elementos nuevos', IconName: 'fa-stethoscope' ); } }
GuΓa rΓ‘pida de modificaciones comunes en verGridT
| QuΓ© quiero hacer | DΓ³nde y cΓ³mo |
|---|---|
| Ocultar una columna de la grilla | GetCellOrderAndVisibility: no la agregues al $newArray |
| Cambiar el orden de las columnas | GetCellOrderAndVisibility: cambia el orden de las lΓneas $newArray[] = ... |
| Cambiar el label de una columna | CustomCell: case "MiCampo": $cell->Label = 'Mi Etiqueta'; return; |
| Ocultar un campo del formulario modal | GetFormOrderAndVisibility: no lo incluyas en el $row |
| Agregar un botΓ³n de acciΓ³n por fila | GetCustomButtons: agrega un new TableButton(...) |
| Cambiar el tΓtulo/Γcono de la pantalla | GetHeader: modifica Title, Description e IconName |
fa-. Ejemplos: fa-stethoscope, fa-user-md, fa-hospital, fa-cog, fa-list.
JS Cliente AJAX β TrazaSvc.js
Este archivo define la clase JavaScript que encapsula todas las llamadas AJAX al Controller API. Sigue el mismo patrΓ³n que el Svc PHP: un mΓ©todo por endpoint. El frontend nunca llama directamente a una URL; siempre usa esta clase.
UbicaciΓ³n del archivo
public/services/mantenedores/Tools/TrazaSvc.js
CΓ³digo completo
class TrazaSvc extends Service { constructor() { // Define el prefijo de ruta del Controller API // Se resuelve como: /api/mantenedores/tools/traza/{metodo} super('mantenedores/tools/traza'); } // Verifica si se puede eliminar (retorna advertencias como HTML) SePuedeEliminarTraza(idTraza, onSuccessCallback = null, onErrorCallback = null) { this.Call( 'SePuedeEliminarTraza', 'post', { IdTraza: idTraza }, onSuccessCallback, onErrorCallback, { title: 'Por favor espere...', text: 'Verificando...' } ); } // Elimina el registro (llamado despuΓ©s de confirmar) EliminarTraza(idTraza, onSuccessCallback = null, onErrorCallback = null) { this.Call( 'EliminarTraza', 'post', { IdTraza: idTraza }, onSuccessCallback, onErrorCallback, { title: 'Por favor espere...', text: 'Eliminando el registro' } ); } // Carga el HTML del popup (nuevo o ediciΓ³n) PopUpTraza(idTraza, nombreIdPadre, valorIdPadre, onSuccessCallback = null, onErrorCallback = null) { this.Call( 'PopUpTraza', 'post', { IdTraza: idTraza, NombreIdPadre: nombreIdPadre, ValorIdPadre: valorIdPadre }, onSuccessCallback, onErrorCallback ); } // Guarda el registro (insert o update) GuardarTraza(entity, onSuccessCallback = null, onErrorCallback = null) { this.Call( 'GuardarTraza', 'post', entity, onSuccessCallback, onErrorCallback, { title: 'Por favor espere...', text: 'Guardando el registro' } ); } // Busca registros y retorna el HTML de la grilla BuscarTraza(entity, onSuccessCallback = null, onErrorCallback = null) { this.Call( 'BuscarTraza', 'post', entity, onSuccessCallback, onErrorCallback ); } } // Factory function usada internamente por el framework function TrazaSvc_invoke() { return new TrazaSvc(); }
Signatura de this.Call()
| ParΓ‘metro | Tipo | DescripciΓ³n |
|---|---|---|
| 1Β° β mΓ©todo | string | Nombre del mΓ©todo en el Controller API (case-sensitive) |
| 2Β° β verbo HTTP | string | Siempre 'post' en este proyecto |
| 3Β° β datos | object | Payload que recibirΓ‘ el Controller como parΓ‘metros |
| 4Β° β onSuccess | function|null | Callback al Γ©xito (result = dato retornado) |
| 5Β° β onError | function|null | Callback al error (errorCode, errorMessage) |
| 6Β° β loadingMsg | object|null | Mensaje del spinner mientras espera (opcional) |
Trait JS β TrazaSvcT.js
public/services/mantenedores/Tools/TrazaSvcT.js
// CΓ³digo adicional para el funcionamiento del mantenedor de: Traza
// Agregar aquΓ mΓ©todos JS adicionales si se necesitan
JS LΓ³gica Frontend β traza.js
Este archivo contiene los event handlers y la lΓ³gica del frontend: quΓ© pasa cuando el usuario hace click en cada botΓ³n. El framework llama automΓ‘ticamente a estas funciones usando la convenciΓ³n {keyDelBoton}_OnClick.
UbicaciΓ³n del archivo
public/scripts/mantenedores/tools/traza.js
CΓ³digo completo
// ββ INICIALIZACIΓN βββββββββββββββββββββββββββββββββββββββββββββββ $(function() { // AquΓ puedes ejecutar cΓ³digo al cargar la pΓ‘gina // Ej: simular click en "Buscar" para mostrar todos al entrar // $('#btnBuscarTraza').trigger("click"); }); // ββ VALIDACIΓN DE CAMPO INDIVIDUAL ββββββββββββββββββββββββββββββ function ValidarTrazaElemento(elemento) { var errores = []; var empty = false; var name = $(elemento).attr('data-property-name'); var label = $(elemento).attr('data-property-label'); if ($(elemento).attr('required') == 'required') { if ($(elemento).IsEmpty()) { errores.push("Este campo es obligatorio"); empty = true; } } // Otras validaciones: RUT, email, telΓ©fono if (!empty && $(elemento).attr('validar-rut') != null) { if (!$(elemento).ValidarRut()) errores.push("El rut ingresado no es vΓ‘lido"); } return { Field: name, Label: label, Cantidad: errores.length, Errores: errores }; } // ββ VALIDACIΓN GENERAL DEL FORMULARIO βββββββββββββββββββββββββββ function ValidarTrazaGeneral(eventInfo) { var result = ""; var erroresGenerales = []; var obj = Object.entries(eventInfo.FormElements); for (var i = 0; i < obj.length; i++) { let elemento = obj[i][1]; let errores = ValidarTrazaElemento(elemento); if (errores.Cantidad > 0) { erroresGenerales.push(errores); $(elemento).addClass('form-error'); } } if (erroresGenerales.length > 0) { result = '<div class="errores-validacion-container">'; erroresGenerales.forEach(function(field) { result += '<div class="error-validacion-container">'; result += '<div class="error-validacion-titulo">' + field.Label + '</div>'; field.Errores.forEach(function(msg) { result += '<div class="error-validacion-detalle">' + msg + '</div>'; }); result += '</div>'; }); result += '</div>'; } return result; } // ββ EVENTO: LIMPIAR FILTROS ββββββββββββββββββββββββββββββββββββββ function btnLimpiarTraza_OnClick(eventInfo) { var obj = Object.entries(eventInfo.FormElements); for (var i = 0; i < obj.length; i++) { let elemento = obj[i][1]; if ($(elemento).is('input') || $(elemento).prop('type') == 'text') { $(elemento).val(''); } else if ($(elemento).prop('type') == 'select-one' || $(elemento).prop('type') == 'select-multiple') { $(elemento).val(-1).change(); } } } // ββ EVENTO: BUSCAR βββββββββββββββββββββββββββββββββββββββββββββββ function btnBuscarTraza_OnClick(eventInfo) { // Mostrar spinner mientras carga $('#formulario-traza').html($('#content-wait-modal').html()); if ($.fn.DataTable.isDataTable('#tbTraza')) { $('#tbTraza').DataTable().destroy(); } Swal.fire({ title: 'Buscando', allowOutsideClick: false, showConfirmButton: false, willOpen: () => Swal.showLoading() }); var formData = eventInfo.FormData; var entity = { RegistroTrazaBuscar: JSON.stringify({ Descripcion: (formData.Descripcion == null) ? '' : formData.Descripcion, }) }; var trazaSvc = new TrazaSvc(); trazaSvc.BuscarTraza( entity, function(result) { Swal.close(); $('#formulario-traza').html(result); β inyecta la grilla }, function(errorCode, errorMessage) { Swal.close(); $('#formulario-traza').html('<div class="note-box">' + errorMessage + '</div>'); } ); } // ββ FUNCIΓN AUXILIAR: abrir popup βββββββββββββββββββββββββββββββ function PopUpTraza(idTraza = 0) { var nombreIdPadre = $('#nombre-id-padre').val() || ""; var valorIdPadre = $('#valor-id-padre').val() || -1; const trazaPopUp = NewPopUp({ dismissOnOutsideClick: true }); var trazaSvc = new TrazaSvc(); trazaSvc.PopUpTraza( idTraza, nombreIdPadre, valorIdPadre, function(result) { $(trazaPopUp).RefreshPopUp(result); β inyecta el HTML del modal }, function(errorCode, errorMessage) { Toast.fire({ icon: 'error', title: errorMessage }); $('.modal').modal('hide'); } ); } // ββ EVENTO: NUEVO ββββββββββββββββββββββββββββββββββββββββββββββββ function btnNuevaTraza_OnClick() { PopUpTraza(); β sin ID β modo nuevo } // ββ EVENTO: EDITAR βββββββββββββββββββββββββββββββββββββββββββββββ function btnEditarTraza_OnClick(rowInfo) { PopUpTraza(rowInfo.RowData.pk); β con ID β modo ediciΓ³n } // ββ EVENTO: GUARDAR (en el popup) ββββββββββββββββββββββββββββββββ function btnGuardarTraza_OnClick(eventInfo) { let formData = eventInfo.FormData; // 1. Validar campos requeridos var validacionGeneral = ValidarTrazaGeneral(eventInfo); if (validacionGeneral != "") { Swal.fire({ title: "Errores en el formulario", html: validacionGeneral, width: 640, icon: 'error' }); return; } // 2. ValidaciΓ³n personalizada (opcional, en trazaT.js) if (typeof ValidarTraza === 'function') { var validacion = ValidarTraza(formData); if (validacion != "") { Toast.fire({ icon: 'error', title: validacion }); return; } } // 3. Armar el payload y llamar al servicio const trazaSvc = new TrazaSvc(); var entity = { TrazaRegistro: JSON.stringify({ IdTraza: formData.IdTraza, Descripcion: formData.Descripcion, IndVigente: formData.IndVigente, IndEliminado: formData.IndEliminado, }) }; trazaSvc.GuardarTraza( entity, function(success) { // Refrescar la grilla $('#btnBuscarTraza').trigger("click"); Swal.fire({ title: 'Β‘Guardado!', text: 'El registro fue guardado correctamente.', icon: "success", confirmButtonText: "OK", }).then(() => { $('.modal').last().modal('hide'); }); }, function(errorCode, errorMessage) { Toast.fire({ icon: 'error', title: errorMessage }); } ); } // ββ EVENTO: ELIMINAR ββββββββββββββββββββββββββββββββββββββββββββ function btnEliminarTraza_OnClick(rowInfo) { const trazaSvc = new TrazaSvc(); var idTraza = rowInfo.RowData.pk; // 1. Verificar si hay advertencias antes de mostrar confirmaciΓ³n trazaSvc.SePuedeEliminarTraza( idTraza, function(result) { var alertWidth = 400; if (result != "") { result = '<span class="swal-warning">' + result + '</span><br><br>'; alertWidth = 640; } // 2. Mostrar confirmaciΓ³n Swal.fire({ title: 'ΒΏEliminar registro?', html: result + '<span>ΒΏConfirmas la eliminaciΓ³n?</span>', showCancelButton: true, width: alertWidth, confirmButtonText: 'SΓ, eliminar', cancelButtonText: 'Cancelar', confirmButtonColor: '#e0001c', cancelButtonColor: '#9b9b9b', }).then((confirmed) => { if (confirmed.isConfirmed) { // 3. Ejecutar eliminaciΓ³n trazaSvc.EliminarTraza( idTraza, function() { DropTableRow('tbTraza', idTraza); β quita la fila de la grilla Toast.fire({ icon: 'info', title: 'Registro eliminado.' }); }, function(errorCode, errorMessage) { Toast.fire({ icon: 'error', title: errorMessage }); } ); } }); }, function(errorCode, errorMessage) { Toast.fire({ icon: 'error', title: errorMessage }); } ); }
ConvenciΓ³n de nombres de funciones
| FunciΓ³n JS | Se llama cuando | CΓ³mo lo sabe el framework |
|---|---|---|
btnBuscarTraza_OnClick(eventInfo) | Click en botΓ³n Key: 'btnBuscarTraza' | Framework construye el nombre: {Key}_OnClick |
btnNuevaTraza_OnClick() | Click en botΓ³n Key: 'btnNuevaTraza' | Mismo patrΓ³n |
btnEditarTraza_OnClick(rowInfo) | Click en botΓ³n de fila Key: 'btnEditarTraza' | Recibe rowInfo.RowData con los atributos data de la fila |
btnGuardarTraza_OnClick(eventInfo) | Click en botΓ³n del formulario popup | eventInfo.FormData tiene los valores del form |
btnEliminarTraza_OnClick(rowInfo) | Click en botΓ³n de fila eliminar | Recibe rowInfo.RowData.pk con el ID del registro |
Trait JS β trazaT.js
public/scripts/mantenedores/tools/trazaT.js
// Extensiones personalizadas del mantenedor de Traza
// Ejemplos de funciones que puedes definir aquΓ:
// ValidaciΓ³n adicional al guardar:
// function ValidarTraza(formData) {
// if (formData.Descripcion.length < 3) return "La descripciΓ³n debe tener al menos 3 caracteres.";
// return "";
// }
// LΓ³gica post-refresco de grilla:
// function CustomRefrescarTrazas(IdTraza) {
// return true; // retornar true ejecuta el refresco estΓ‘ndar
// }
JS Registro del Bundle JavaScript
Para que la vista pueda incluir los archivos JS del mantenedor con la directiva @@IncludeScriptBundle(mantenedorTrazaJS), ese bundle debe estar registrado en el archivo de configuraciΓ³n central.
UbicaciΓ³n del archivo
Application/Configuration/bundles.config.json
Entrada a agregar
Busca el archivo JSON y agrega la siguiente entrada en el objeto raΓz:
{
// ... otras entradas existentes ...
"mantenedorTrazaJS": {
"IdBundle": "mantenedorTrazaJS",
"Tipo": "javascript",
"Versionado": true,
"BundleLocation": "scripts",
"Sources": [
{ "Folder": "mantenedores/tools", "File": "traza.js" },
{ "Folder": "mantenedores/tools", "File": "trazaT.js" }
]
}
}
ΒΏQuΓ© hace cada campo?
| Campo | Valor | DescripciΓ³n |
|---|---|---|
IdBundle | mantenedorTrazaJS | ID ΓΊnico del bundle β debe coincidir con el nombre de la clave y con @@IncludeScriptBundle(...) |
Tipo | javascript | Indica que son archivos JS |
Versionado | true | El framework agrega un hash al nombre para forzar cachΓ© bust al cambiar |
BundleLocation | scripts | Carpeta base dentro de public/scripts/ |
Sources[].Folder | mantenedores/tools | Subcarpeta dentro de BundleLocation |
Sources[].File | traza.js | Nombre del archivo |
ServiceJS bundle ya incluye todos los archivos *.js de la carpeta services/mantenedores/Tools/ con un wildcard, por lo que TrazaSvc.js ya estΓ‘ incluido automΓ‘ticamente en el bundle global. Solo necesitas registrar los archivos de scripts/ (la lΓ³gica de eventos).
β Checklist Final
Usa esta lista para verificar que no falta ningΓΊn archivo antes de probar el mantenedor en el navegador.
Capa DAO β 8 archivos
Application/Dao/Entities/Tools/Traza.phpApplication/Dao/Entities/Tools/TrazaT.phpApplication/Dao/Entities/Tools/VWTraza.phpApplication/Dao/Entities/Tools/VWTrazaT.phpApplication/Dao/Services/Tools/TrazaDao.phpApplication/Dao/Services/Tools/TrazaDaoT.phpApplication/Dao/Services/Tools/VWTrazaDao.phpApplication/Dao/Services/Tools/VWTrazaDaoT.php
Capa BLL β 10 archivos
Application/BLL/DataTransferObjects/Tools/TrazaDto.phpApplication/BLL/DataTransferObjects/Tools/TrazaDtoT.phpApplication/BLL/DataTransferObjects/Tools/VWTrazaDto.phpApplication/BLL/DataTransferObjects/Tools/VWTrazaDtoT.phpApplication/BLL/Services/Tools/TrazaSvc.phpApplication/BLL/Services/Tools/TrazaSvcT.phpApplication/BLL/Services/Tools/VWTrazaSvc.phpApplication/BLL/Services/Tools/VWTrazaSvcT.phpApplication/BLL/BusinessObjects/Mantenedores/Tools/TrazaBO.phpApplication/BLL/BusinessObjects/Mantenedores/Tools/TrazaBOT.php
Controllers β 4 archivos
Application/Controllers/Mantenedores/Tools/TrazaController.phpApplication/Controllers/Mantenedores/Tools/TrazaControllerT.phpApplication/Controllers/Api/Mantenedores/Tools/TrazaController.phpApplication/Controllers/Api/Mantenedores/Tools/TrazaControllerT.php
Vistas PHP β 4 archivos
Application/Views/Mantenedores/Tools/Traza/verView.phpApplication/Views/Mantenedores/Tools/Traza/verGridView.phpApplication/Views/Mantenedores/Tools/Traza/popupView.phpApplication/Views/Mantenedores/Tools/Traza/verGridT.php
JavaScript β 4 archivos
public/services/mantenedores/Tools/TrazaSvc.jspublic/services/mantenedores/Tools/TrazaSvcT.jspublic/scripts/mantenedores/tools/traza.jspublic/scripts/mantenedores/tools/trazaT.js
ConfiguraciΓ³n β 1 entrada en JSON
- Entrada
mantenedorTrazaJSenApplication/Configuration/bundles.config.json
Checklist de funcionamiento
- La URL del mantenedor carga sin error 500
- El formulario de bΓΊsqueda se muestra correctamente
- Al hacer click en "Buscar" aparece la grilla con los registros
- Al hacer click en "Nuevo" se abre el popup con el formulario vacΓo
- Al guardar un nuevo registro, aparece en la grilla
- Al hacer click en "Editar" de una fila, el popup se abre con los datos del registro
- Al guardar los cambios, la grilla se actualiza
- Al hacer click en "Eliminar", aparece el diΓ‘logo de confirmaciΓ³n
- Al confirmar la eliminaciΓ³n, la fila desaparece de la grilla
- El campo IndVigente se muestra como checkbox solo en modo ediciΓ³n
π Referencia RΓ‘pida β Convenciones de Nombres
| Artefacto | PatrΓ³n | Ejemplo |
|---|---|---|
| Entity (tabla) | {Nombre} | Traza |
| Entity (vista) | VW{Nombre} | VWTraza |
| DAO | {Nombre}Dao | TrazaDao |
| DTO | {Nombre}Dto | TrazaDto |
| Service BLL | {Nombre}Svc | TrazaSvc |
| Business Object | {Nombre}BO | TrazaBO |
| Trait de cualquiera | {ClaseBase}T | TrazaBOT |
| Controller Web | {Nombre}Controller | TrazaController |
| Controller API | {Nombre}Controller (distinto namespace) | Api\Mantenedores\Tools\TrazaController |
| Vista principal | verView.php | Views/Mantenedores/Tools/Traza/verView.php |
| Vista grilla | verGridView.php | Views/Mantenedores/Tools/Traza/verGridView.php |
| Vista popup | popupView.php | Views/Mantenedores/Tools/Traza/popupView.php |
| Template grilla | verGridT.php | Views/Mantenedores/Tools/Traza/verGridT.php |
| Svc JS | {Nombre}Svc.js | public/services/mantenedores/Tools/TrazaSvc.js |
| Script JS | {nombre-minuscula}.js | public/scripts/mantenedores/tools/traza.js |
| Bundle JS | mantenedor{Nombre}JS | mantenedorTrazaJS |