¿Está la lógica de negocio Delphi atrapada en sus formularios?
Localice y separe la lógica de negocio Delphi oculta en formularios VCL, controles de datos, eventos de dataset y código de transacciones.

Los formularios de Delphi suelen ser especificaciones ejecutables vestidas de interfaz de usuario. Un clic en un botón calcula descuentos, TDBEdit.OnExit normaliza un código de cuenta, BeforePost rechaza un periodo cerrado y un evento de la cuadrícula cambia lo que devolverá la siguiente consulta. Si trata esos manejadores como código de presentación desechable, el sustituto parecerá terminado mientras altera el negocio sin avisar.
Una migración directa empeora el riesgo. Recrear cada formulario en un navegador o en otro toolkit de escritorio conserva los límites antiguos y después obliga a redescubrir sus dependencias ocultas en un modelo de eventos menos tolerante. La ruta más segura consiste en identificar el comportamiento observable, extraer las reglas tras interfaces explícitas y permitir que la antigua aplicación VCL y el sistema nuevo llamen a las mismas operaciones conceptuales durante la transición.
¿Cómo acabó la lógica de negocio dentro de los formularios Delphi?
La lógica de negocio acabó en los formularios Delphi porque la VCL acortaba muchísimo el camino hasta tener software operativo. Se colocan un dataset, un TDataSource, algunos controles conscientes de los datos y un botón en un formulario, y luego se pone la decisión junto al evento que la necesita. La elección era razonable cuando un desarrollador controlaba la aplicación y los usuarios estaban cerca de la base de datos. Años de cambios convirtieron la proximidad en arquitectura.
La clase del formulario fue acumulando varias responsabilidades. Lee el estado de los controles, interpreta la intención del usuario, aplica políticas, inicia transacciones, actualiza datasets, da formato a mensajes y decide qué pantalla se abre después. El archivo .dfm añade otra capa porque los valores de propiedades y las conexiones entre componentes modifican el comportamiento en ejecución sin aparecer en el método Pascal que está leyendo.
Por eso el número de líneas no refleja el tamaño de la migración. Una unidad de 250 líneas puede depender de decenas de propiedades heredadas, campos persistentes, acciones, módulos de datos compartidos y asignaciones de eventos guardadas en el DFM. Incluso un TDBEdit aparentemente vacío escribe a través de TDataSource en el búfer de un dataset. Su comportamiento depende del estado del dataset, de los eventos de campo, de las máscaras de edición y de lo que haga BeforePost más tarde.
No clasifique todo el código de eventos como lógica de negocio. Mostrar un diálogo, cambiar el foco y ajustar el ancho de las columnas son tareas de presentación. Decidir que una factura no puede contabilizarse después del cierre de un periodo es una regla. Convertir el nivel de un cliente en un descuento es un cálculo. Llamar a un procedimiento almacenado puede ser orquestación de la aplicación o acceso a datos, según el contrato que exponga el procedimiento. La distinción importa porque cada clase de código necesita un destino diferente.
Una prueba útil consiste en eliminar mentalmente el formulario. Si la decisión debe seguir siendo válida para una importación, una petición de API o un proceso por lotes, se trata de lógica de negocio. Si el comportamiento solo ayuda a una persona a manejar esta pantalla concreta, puede quedarse en la interfaz. Si coordina un caso de uso sin contener una regla, pertenece a un servicio de aplicación.
El archivo del formulario es solo la mitad del programa
Necesita un inventario del comportamiento ejecutable antes de extraer nada, y debe incluir Pascal, recursos DFM, formularios heredados y objetos de base de datos. Buscar solo manejadores de clic deja fuera las ediciones automáticas y los eventos del ciclo de vida. Leer únicamente el DFM omite los manejadores asignados durante la ejecución.
Empiece con un mapa mecánico. Para cada formulario, anote todos los eventos de componentes y datasets, acciones, temporizadores, manejadores de mensajes y llamadas que cruzan a otra unidad. Incluya OnCreate, OnShow, OnCloseQuery, OnChange, OnExit, OnClick, OnExecute, BeforeEdit, BeforePost, AfterPost, OnCalcFields y los manejadores de excepciones. Busque asignaciones como Button.OnClick :=, porque algunas aplicaciones vuelven a conectar el comportamiento después de construir los objetos.
Añada después los caminos implícitos. Registre qué controles apuntan a cada TDataSource, qué dataset expone cada fuente, si AutoEdit está activo y qué objetos TField persistentes tienen eventos de validación o cambio. La documentación de TDataSource de Embarcadero describe el componente como el conducto entre un dataset y los controles conscientes de los datos. Esa palabra modesta, conducto, es la advertencia: una pulsación puede cruzar el límite de la interfaz antes de que se ejecute cualquier botón Guardar.
Cree una tabla con una fila por comportamiento observado, no una fila por método. Estas columnas descubren la mayoría de las trampas:
Trigger Reads Writes Rule owner
btnPostClick invoice fields, role invoice status posting policy
AmountFieldValidate amount, currency record buffer money rule
CustomerDataChange current customer filter params query orchestration
La columna Evidence obliga a ser preciso. El nombre de un manejador no es una prueba. Capture los valores de entrada, el estado del dataset, las llamadas SQL, las filas devueltas, los mensajes y los valores finales almacenados. Si un manejador depende del orden en que se disparan los eventos de VCL, registre el orden. Esa secuencia forma parte del comportamiento hasta que demuestre que usuarios e integraciones no pueden observarla.
Los formularios heredados merecen una revisión aparte. Un DFM hijo puede sobrescribir una propiedad y heredar al mismo tiempo un evento de un antecesor situado en otro directorio del proyecto. La unidad hija puede parecer inofensiva aunque el formulario base abra datasets o cambie permisos en OnShow. Despliegue la cadena de herencia y registre la configuración efectiva de componentes en la aplicación compilada, no solo el texto de un archivo.
Las acciones también ocultan reutilización. Un mismo TAction.OnExecute puede activarse desde una opción de menú, un botón de la barra de herramientas y un atajo, mientras OnUpdate decide su disponibilidad a partir del estado global. Si el cliente nuevo copia solo el botón visible, quienes usan el teclado pueden perder una ruta y la autorización puede terminar reducida a un indicador visual de desactivación. Trate el permiso de ejecución como una regla en el límite del comando y el estado habilitado como una vista de esa decisión.
Extraiga las decisiones antes de mover los controles
Extraiga primero las decisiones y los cálculos puros porque ofrecen puntos de separación estables sin alterar la pantalla. Deje el manejador de eventos en su sitio, pero redúzcalo a recoger la entrada, llamar a una regla y mostrar el resultado. La aplicación en funcionamiento sigue siendo útil mientras la regla puede invocarse sin formulario.
Suponga que un formulario de pedidos calcula una decisión de crédito dentro de btnApproveClick. El manejador original lee campos, comprueba un indicador del cliente, compara totales, actualiza controles, publica el dataset y muestra un mensaje. Separe la decisión de esos efectos:
type
TApprovalInput = record
OrderTotal: Currency;
CreditLimit: Currency;
AccountOnHold: Boolean;
end;
TApprovalDecision = record
Allowed: Boolean;
ReasonCode: string;
end;
function DecideApproval(const Input: TApprovalInput): TApprovalDecision;
begin
if Input.AccountOnHold then
Exit(TApprovalDecision.Create(False, 'ACCOUNT_HOLD'));
if Input.OrderTotal > Input.CreditLimit then
Exit(TApprovalDecision.Create(False, 'LIMIT_EXCEEDED'));
Result := TApprovalDecision.Create(True, 'APPROVED');
end;
La sintaxis exacta del record quizá necesite ajustes para la versión de Delphi instalada. Lo importante es el diseño: la función acepta valores, devuelve una decisión y no sabe nada de TEdit, TField, resultados modales ni transacciones. Una prueba unitaria puede cubrirla, una importación por lotes puede llamar a una operación equivalente y un servicio de destino puede implementar el mismo contrato.
No traslade el manejador antiguo sin cambios a una clase llamada TOrderService. Un método que acepta un formulario o atraviesa un módulo de datos global sigue acoplado a la interfaz. Pasar veinte controles como parámetros solo esconde ese acoplamiento en la firma. Defina las entradas con términos del negocio e incluya el contexto suficiente para que la decisión sea determinista.
Resista también la tentación de extraer utilidades compartidas demasiado pronto. Dos manejadores que calculan impuestos pueden diferir porque uno trata abonos y el otro facturas anteriores a un cambio de reglas. Primero caracterice los dos comportamientos. Unifíquelos solo cuando las pruebas indiquen que la diferencia es accidental.
Los controles de datos crean una ruta de escritura invisible
Los controles conscientes de los datos necesitan un modelo de edición explícito en el sustituto porque combinan visualización, navegación, búfer, validación y persistencia. Un campo del navegador enlazado a JSON no equivale a un TDBEdit conectado mediante TDataSource a un TDataSet activo.
El primer comportamiento oculto es la entrada en modo edición. Embarcadero documenta que TDataSource.AutoEdit vale true de forma predeterminada y llama al método Edit del dataset cuando un usuario intenta modificar un control enlazado. Por tanto, la aplicación original puede bloquear una fila, marcar un registro como modificado o activar las acciones Post y Cancel con la primera pulsación. Una interfaz nueva que espera a un Guardar explícito tiene otro modelo de concurrencia, aunque los campos parezcan idénticos.
El segundo comportamiento oculto es el búfer. Un valor mostrado puede no ser ni el último valor confirmado en la base ni el que ve otro control después de un evento. Edit, Insert, Post, Cancel, las actualizaciones en caché y la configuración del proveedor determinan cuándo son duraderos los cambios. Escriba esos estados como una pequeña máquina de estados. Por ejemplo: View permite navegar; Edit guarda un borrador local; Saving valida el borrador y envía un comando; Conflict conserva el borrador del usuario mientras muestra la versión más reciente del servidor.
El tercer comportamiento es la ubicación de la validación. Una máscara comprueba caracteres durante la entrada. El OnValidate de un campo revisa un valor completo justo antes de que entre en el búfer del registro. BeforePost puede inspeccionar el registro entero. Las restricciones de la base actúan después y pueden cubrir relaciones que ningún formulario conoce. La documentación de TField.OnValidate de Embarcadero indica expresamente que la asignación mediante código evita EditMask, mientras OnValidate sigue comprobando el campo antes de publicarlo. Es una buena razón para llevar las reglas duraderas por debajo del nivel del widget.
Use cuatro grupos al reubicar la validación:
- La ayuda de entrada, como el formato y la respuesta inmediata a caracteres, permanece en el cliente.
- Los invariantes de campo, como un conjunto de códigos permitido, residen en la operación de dominio y también pueden ejecutarse en el cliente para responder rápido.
- Las reglas entre campos y de autorización se ejecutan en el servidor o en el límite de la aplicación que controla el comando.
- Postgres sigue imponiendo las restricciones referenciales y de unicidad aunque antes se ejecuten comprobaciones más amables.
Duplicar una regla para dar respuesta inmediata solo es aceptable cuando una implementación conserva la autoridad. El servidor debe rechazar un comando no válido al margen de lo que haya comprobado el cliente. De lo contrario, una importación, una integración o un cliente desactualizado puede saltarse el negocio.
El enlace maestro-detalle añade otra trampa. Mover el cursor maestro puede cambiar parámetros y actualizar filas de detalle de forma automática. Los usuarios pueden verlo como un único espacio de trabajo coherente, pero la implementación depende de la posición del cursor en vez de un identificador explícito. El sustituto debe solicitar los detalles por ID maestro, mantener la selección en el cliente y decidir qué sucede con un borrador de detalle sin guardar cuando cambia el maestro.
Los campos calculados y de búsqueda también necesitan un propietario. Un campo calculado que solo sirve para mostrar datos pertenece a una proyección de consulta o un modelo de vista. Si otra regla lo lee, lleve el cálculo a la operación de dominio y pruebe sus entradas. Un campo de búsqueda puede ocultar un viaje a la base o un valor de caché antiguo, así que registre si el comportamiento actual ve datos en directo, datos del momento de apertura o datos renovados por un evento concreto.
Los eventos de dataset no son un modelo de dominio
Mover datasets a un TDataModule mejora la organización, pero no separa por sí solo la lógica de negocio. Embarcadero describe TDataModule como un lugar donde centralizar componentes no visuales e incluso permite poner allí reglas de negocio. Ese consejo ordena los formularios. No crea límites, entradas explícitas ni casos de uso que puedan probarse por separado.
Los eventos del dataset suelen mezclar tres trabajos. BeforePost puede validar un invariante, rellenar campos de auditoría y ejecutar otra consulta. AfterScroll puede actualizar un dataset de detalle y habilitar una acción. OnCalcFields puede calcular un valor de presentación que otro manejador tratará después como autoritativo. Copiar esos eventos a un repositorio o a un hook del ORM recrea la misma ambigüedad.
Clasifique cada evento por lo que lo provoca y lo que garantiza. Una operación de dominio debe ejecutarse porque un llamante solicitó ApproveOrder, no porque un dataset genérico acabó publicándose. Un repositorio debe guardar un pedido aprobado, no decidir si la aprobación está permitida. Un modelo de vista puede calcular texto visible, pero los totales persistidos deben venir de la regla propietaria de los cálculos monetarios.
Hay un caso incómodo: un componente externo puede llamar a Post directamente y depender de BeforePost para proteger el registro. No elimine esa defensa durante la extracción. Coloque la regla en una unidad invocable, haga que BeforePost la llame y dirija los comandos nuevos por la misma regla. Quite el evento antiguo solo cuando las trazas demuestren que todas las rutas de escritura usan el límite nuevo.
Los módulos de datos globales exigen un cuidado especial. Un formulario puede suponer que dmMain.qryCustomer ya está abierto, colocado en el mismo cliente y dentro de una transacción iniciada en otro lugar. Eso es estado mutable compartido. Convierta esas precondiciones en identificadores y ámbitos de transacción explícitos. Pasar CustomerId es más seguro que pasar la fila actual de un dataset cuyo cursor puede mover otro evento.
Las transacciones deben seguir el caso de uso
Los límites de transacción deben rodear una operación de negocio, no un manejador de botón o cada publicación del dataset. El código antiguo puede iniciar una transacción en un evento, tocar varios datasets mediante llamadas anidadas y confirmar en otro. Repartir esa secuencia entre peticiones HTTP puede dejar trabajo parcial que la aplicación de escritorio nunca habría permitido.
Trace una operación correcta y cada fallo relevante. Registre las sentencias SQL, las llamadas a procedimientos almacenados, los identificadores generados, el comportamiento de los bloqueos y el punto de commit o rollback. Después nombre la operación con lenguaje de negocio. ClosePeriod podría actualizar el registro del periodo, crear apuntes contables y rechazar borradores pendientes. Esos cambios pertenecen a un solo comando de aplicación, aunque la VCL llegue hasta ellos desde tres formularios.
Defina una petición y un resultado antes de elegir los detalles de transporte:
{
"operation": "ApproveOrder",
"order_id": 4812,
"expected_version": 17,
"actor_id": 204,
"decision_input": {
"order_total": "1250.00",
"currency": "EUR"
}
}
Un resultado correcto debe devolver la nueva versión, el estado resultante y códigos de motivo estables. Un conflicto debe devolver la versión actual sin sobrescribirla en silencio. El valor decimal es una cadena para impedir que una decisión monetaria se convierta en un error de coma flotante en el cliente TypeScript.
No exponga un endpoint UpdateOrder genérico que acepte todas las columnas. Traslada la abstracción del dataset a través de la red e invita a los llamantes a crear estados que antes impedía el formulario. Comandos como ApproveOrder, ReleaseHold y ChangeDeliveryDate expresan intención y dan a cada transacción un límite defendible.
Los procedimientos almacenados complican la propiedad, pero no el método. Si un procedimiento contiene reglas, caracterice sus entradas, salidas, cambios y errores como parte del sistema actual. Al principio, manténgalo tras un adaptador. Reescríbalo solo cuando las pruebas de paridad cubran su comportamiento, sobre todo si el código Delphi interpreta códigos de error del proveedor o depende de efectos secundarios de triggers.
Las pruebas de caracterización son la primera especificación
Las pruebas de caracterización deben comparar los resultados observables de la aplicación antigua con los de la operación extraída o reescrita. Las pruebas unitarias redactadas a partir de requisitos recordados serán útiles después, pero no pueden mostrar qué comportamiento no documentado ya necesita el negocio.
Capture tráfico de producción representativo cuando las políticas lo permitan, retire o proteja la información sensible y convierta cada operación en un caso reproducible. En una aplicación de escritorio, el tráfico es más que peticiones de red. Registre el estado inicial de la base o un fixture estable, las entradas del usuario, los permisos pertinentes, la acción invocada, los mensajes o códigos de motivo, los efectos SQL y las filas finales. Añada casos de cancelación, doble clic, registros obsoletos, nulos, límites de redondeo y fallos de base de datos.
Un fixture compacto facilita la revisión:
case: approve-order-over-limit
given:
order_id: 4812
order_total: "1250.00"
credit_limit: "1000.00"
account_on_hold: false
when: ApproveOrder
expect:
allowed: false
reason_code: LIMIT_EXCEEDED
order_status: DRAFT
committed_writes: 0
Ejecute el caso contra una instancia controlada de la ruta Delphi y contra la ruta nueva. Compare resultados de negocio, estado persistido y efectos secundarios pertinentes. No compare diferencias accesorias como marcas de tiempo generadas salvo que afecten a un contrato. Normalice identificadores creados por la base cuando la propia identidad no sea significativa.
Las pruebas de patrón oro tienen límites. El sistema antiguo puede equivocarse, y preservar todos sus defectos a ciegas los congela. Etiquete las discrepancias como paridad esperada, corrección aprobada o diferencia sin resolver. Una corrección aprobada requiere un responsable identificado y una prueba del comportamiento previsto. De lo contrario, los desarrolladores llamarán correcciones a las sorpresas y los revisores no podrán distinguir migración de rediseño.
El tiempo y la configuración regional merecen fixtures deliberados. Las aplicaciones Delphi suelen convertir fechas mediante ajustes del puesto de trabajo y redondear moneda en distintos puntos mediante tipos de base de datos, tipos de campo y formatos de presentación. Incluya valores de final del día, cambios de horario cuando importen las marcas de tiempo, mitades decimales, cadenas vacías y nulos. Compruebe valores almacenados y códigos de motivo en lugar de etiquetas con formato, salvo que la etiqueta forme parte de un documento contractual.
Pruebe también la supresión de eventos. El código puede deshabilitar controles de forma temporal, desconectar un evento o activar una marca de carga para impedir actualizaciones recursivas. La operación nueva no debe reproducir esos trucos mecánicos, pero su resultado final tiene que coincidir. Una reproducción que solo registra la petición correcta y la fila final puede omitir efectos secundarios duplicados a mitad de la secuencia.
CodeHero usa aquí un arnés de paridad frente al tráfico de producción grabado. El principio útil no depende de nuestra plataforma: una sustitución es un problema de pruebas y el parecido entre pantallas es una prueba débil.
Una migración directa de la interfaz conserva el límite caro
Una migración directa de la interfaz suele ser la ruta más cara porque reconstruye pantallas antes de descubrir las operaciones que hay debajo. Los equipos reproducen pestañas, diálogos modales, cuadrículas y navegación y luego los conectan a endpoints CRUD genéricos. Cada regla oculta aparece tarde como un defecto de interfaz, una excepción de API o una discusión sobre lo que significaba Guardar.
La pantalla copiada también lleva supuestos de escritorio a un sistema distribuido. La aplicación VCL puede mantener un cursor de dataset activo, compartir una conexión y responder de forma sincrónica a eventos de campo. Un cliente web tiene latencia, reintentos, cambios concurrentes, sesiones caducadas y peticiones que pueden llegar dos veces. Simular un dataset con estado sobre HTTP produce API habladoras y lógica de cliente frágil.
Por eso, la paridad de píxeles es el criterio de aceptación equivocado. Conserve la paridad de tareas y de negocio. El usuario debe seguir pudiendo aprobar el pedido correcto, ver por qué falló una decisión, recuperarse de un conflicto y completar el trabajo con la información necesaria. La pantalla nueva puede combinar diálogos antiguos o eliminar pasos de navegación si la operación y sus controles siguen claros.
Hay casos en que una migración ligera de la interfaz tiene sentido. Si el objetivo inmediato es la compatibilidad con el sistema operativo, la base de datos y las integraciones no cambiarán y el formulario contiene poca lógica de negocio, un sustituto de escritorio compatible puede ganar tiempo. Trátelo como contención con una vida útil declarada. No lo llame separación arquitectónica.
Para la mayoría de los sistemas longevos, defina el destino alrededor de comandos, consultas y estado explícito. Los servicios Go encajan con las operaciones de aplicación transaccionales y el acceso a datos. Rust tiene sentido en núcleos numéricos donde el comportamiento exacto y el rendimiento merecen un límite estrecho. Los clientes TypeScript deben controlar el estado de interacción y la presentación, mientras Postgres impone restricciones relacionales duraderas. Estas son opciones descritas en el contexto de proyecto suministrado, no una prescripción de que todo entorno Delphi necesite las cuatro.
Cambie por operación, no por formulario
Cambie una operación de negocio cada vez porque los formularios rara vez coinciden con límites limpios de servicios. Un formulario de pedido puede contener búsqueda de clientes, precios, aprobación, impresión y pagos. Sustituir el formulario entero obliga a que las cinco rutas estén listas a la vez y crea una gran unidad de reversión.
Elija una operación con entradas claras, resultados medibles y poco estado compartido. Ponga una interfaz delante de la implementación antigua y después añada la nueva tras el mismo contrato. El formulario VCL puede llamar a ese límite antes de que exista el nuevo cliente. Así obtiene pruebas de que la separación funciona sin ligar la extracción del dominio a una reconstrucción visual.
Una secuencia práctica tiene cinco partes:
- Registre el comportamiento actual y los casos de fallo de la operación seleccionada.
- Extraiga las entradas de reglas y los códigos de resultado mientras el formulario aún controla la presentación.
- Coloque la persistencia tras un adaptador y defina el límite de la transacción.
- Reproduzca casos de paridad contra ambas implementaciones y clasifique cada diferencia.
- Dirija un conjunto controlado de llamadas a la ruta nueva, con un interruptor explícito de reversión.
Evite las escrituras dobles salvo que pueda hacerlas idempotentes y conciliarlas. Leer de los dos sistemas para comparar es más seguro que permitir que ambos modifiquen datos autoritativos. Si la ejecución en sombra dispara correos, pagos, trabajos de impresión o entradas de auditoría, sustituya esos efectos por registradores en el entorno de comparación.
Haga que la reversión sea una decisión de enrutamiento por operación. Si falla la nueva ruta de aprobación, devuelva la aprobación a la implementación antigua sin revertir búsquedas de clientes que ya se hayan trasladado. Mantenga explícita la compatibilidad de la base mientras funcionen las dos rutas. Un cambio de esquema que impida al ejecutable antiguo leer una fila elimina la reversión aunque todavía exista el indicador de función.
Observe los resultados de negocio durante el cambio. Cuente códigos de motivo, conflictos, cancelaciones y operaciones completadas y compare sus distribuciones con la referencia grabada. Las tasas brutas de error son demasiado imprecisas: una ruta nueva puede devolver éxito HTTP mientras aprueba pedidos que las reglas antiguas rechazaban. Investigue los cambios de decisión antes de ampliar el tráfico.
La dependencia más difícil debe determinar el orden. Si la aprobación depende de los precios, extraiga primero los precios o manténgalos tras un adaptador al que llamen ambas versiones. No migre pantallas fáciles dejando la transacción central como sorpresa final. Eso da progreso visible y reduce poco el riesgo.
CodeHero lee toda la base de código Delphi, incluidos los lenguajes conectados, y moderniza la arquitectura mientras mantiene el comportamiento fiel al original. En un programa manual se aplica la misma disciplina: mapear globalmente, cortar localmente y no deducir seguridad de una pantalla compilada.
El sustituto debe impedir el estado oculto
El destino está listo cuando las decisiones de negocio ya no dependen de un control, de la fila actual del dataset, del orden de creación de formularios ni de una transacción global implícita. Debe poder invocar una operación con entrada serializada, observar un resultado estable y probarla sin construir una ventana.
Ese estándar descubre extracciones incompletas. Un servicio que lee Screen.ActiveForm, un repositorio que ejecuta políticas en un hook genérico de guardado o un cliente TypeScript que calcula el total autoritativo de una factura conserva el problema antiguo. Los nombres han cambiado, pero el comportamiento sigue oculto tras eventos de infraestructura.
Mantenga una breve lista de límites en la revisión de código:
- ¿Acepta la operación valores e identificadores de negocio en vez de controles o cursores de dataset?
- ¿Puede ejecutarse cada regla duradera para llamadas desde la interfaz, una importación y una API?
- ¿Cubre una transacción todo el cambio de negocio?
- ¿Son explícitos los resultados de concurrencia y reintento?
- ¿Puede un caso grabado demostrar paridad sin comparar píxeles?
Parte del comportamiento seguirá en la carcasa VCL durante la transición, y está bien. La gestión del foco, los atajos de teclado, la disposición y la presentación del borrador local no necesitan una abstracción prematura. El límite que debe defender es la autoridad: la presentación puede proponer y explicar un cambio, pero una operación de aplicación lo decide y lo confirma.
No empiece redibujando el formulario más grande. Elija la operación que contiene cuyo fallo resulta más caro, capture las pruebas actuales y póngale un nombre. Cuando esa operación pueda ejecutarse sin el formulario, el resto de la modernización tendrá un límite sobre el que construir.
Preguntas frecuentes
¿Cómo sé si un manejador de eventos Delphi contiene lógica de negocio?
Imagine que llama a la misma operación desde una importación o API sin construir el formulario. Las decisiones que deben seguir vigentes son reglas de negocio; los cambios de foco, diálogos y disposición siguen siendo presentación.
¿Debo mover primero el código del formulario a un TDataModule?
Un TDataModule puede despejar el formulario, pero no crea un límite de negocio. Mueva las reglas a unidades con valores de entrada y resultados explícitos y haga que formulario y módulo las llamen.
¿Es seguro sustituir controles VCL de datos por campos web normales?
Solo después de modelar sus comportamientos ocultos de edición, búfer, validación, publicación y cancelación. Que los campos se parezcan en pantalla no garantiza la misma concurrencia ni persistencia.
¿Dónde deben ir las reglas OnValidate de Delphi en un sistema nuevo?
Ponga los invariantes duraderos de campo en la operación de aplicación o dominio que use cada llamante. El cliente puede repetir una comprobación para responder rápido, pero no puede ser la autoridad.
¿Cómo migro de forma segura la lógica de BeforePost?
Extraiga la regla a una unidad invocable y mantenga BeforePost llamándola mientras existan rutas antiguas de escritura. Quite el evento solo cuando las trazas prueben que toda escritura pasa por el límite nuevo.
¿Por qué cuesta tanto una migración directa de la interfaz Delphi?
Reconstruye pantallas antes de descubrir las operaciones y el estado implícito bajo ellas. El equipo paga la reproducción visual y después los cambios de interfaz y API que fuerzan las reglas ocultas.
¿Tengo que conservar todos los defectos para lograr paridad?
No. Clasifique cada diferencia como paridad necesaria, corrección aprobada o comportamiento sin resolver. Una corrección necesita responsable y prueba para que la migración no se convierta en un rediseño sin revisar.
¿Pueden las pruebas unitarias sustituir casos de producción grabados?
Las pruebas unitarias demuestran las reglas que conoce y puede escribir. Los casos grabados descubren órdenes de eventos, formas de datos y efectos olvidados, así que use ambos con fines distintos.
¿Debe la API nueva exponer endpoints CRUD genéricos?
Evite actualizaciones genéricas en registros con muchas reglas. Comandos como ApproveOrder expresan intención, fijan el límite transaccional e impiden que un llamante construya estados inválidos campo a campo.
¿Cuál es la unidad más segura para cambiar una aplicación Delphi?
Cambie una operación de negocio con nombre, entradas, resultados y reversión claros, aunque abarque varios formularios. Un formulario entero suele ser demasiado amplio y un manejador de campo demasiado estrecho.