Catálogo de problemas reales al migrar sistemas Oracle Forms (11g/12c) a stack web Java. Estos problemas son distintos a J2EE y Spring legacy porque Forms es un paradigma propio.
Problema: los archivos .fmb (Forms Module Binary) son binarios. La lógica de triggers (PL/SQL embebido) NO es visible sin extracción.
Manifestación: sistemas en producción 15-20 años donde nadie sabe qué hace exactamente cierto botón.
Trampa: asumir que toda la lógica está en packages PL/SQL de BD. La realidad: 20-40% suele estar en triggers de Forms.
Solución:
- Extraer todos los
.fmba XML confrmf2xml(requiere licencia Forms Builder) o script JDAPI - Inventariar exhaustivamente cada trigger antes de migración
- Catalogar reglas de negocio extraídas con cita a
<form>:<trigger>:<línea>
Problema: Oracle Forms genera automáticamente INSERT/UPDATE/DELETE para los base-table blocks cuando el usuario navega y commit. NO hay código SQL explícito.
Manifestación: un formulario de "mantenimiento de clientes" no tiene código SQL visible para insertar/actualizar; Forms lo hace en background.
Trampa: asumir que la migración a Java se limita a "reescribir lo que está visible". La realidad: cada CRUD debe ser código explícito.
Solución:
- Cada base-table block → Entity JPA + Repository + Service
- Cada commit del form → llamada explícita a
service.save(),service.update(),service.delete() - Validar constraints que se aplicaban con DML automático (NOT NULL, FK) ahora deben ser explícitos en validators
Problema: PL/SQL trata NULL = NULL como NULL (no como TRUE ni FALSE). Java trata null == null como TRUE.
Manifestación:
-- PL/SQL
IF a = b THEN ... -- FALSE si ambos son NULL
IF a IS NULL AND b IS NULL THEN ... -- TRUE si ambos son NULL// Java
a.equals(b) // NullPointerException si a es null
Objects.equals(a, b) // TRUE si ambos nullTrampa: reescribir validaciones PL/SQL → Java sin considerar NULL puede cambiar comportamiento sutilmente. El test de paridad puede no detectarlo si los casos NULL no se prueban explícitamente.
Solución:
- Tests específicos para casos NULL en cada regla migrada
- Uso consistente de
Objects.equals()yObjects.isNull()en Java - Documentar cada caso donde la semántica difiere
Problema: triggers de Forms se disparan en orden específico según la acción del usuario:
Insertar nuevo registro:
WHEN-CREATE-RECORD → PRE-INSERT → POST-INSERT → ...
Validar item:
WHEN-VALIDATE-ITEM → POST-CHANGE → WHEN-VALIDATE-RECORD
Manifestación: un PRE-INSERT que calcula un campo basándose en un valor que se setea en WHEN-CREATE-RECORD.
Trampa: reescribir triggers individualmente sin considerar la dependencia de orden. El service Java equivalente puede tener el código en orden incorrecto.
Solución:
- Documentar firing sequence relevante en cada feature
- Service Java replica el orden explícitamente
- Tests que validan el orden (no solo el resultado final, sino los estados intermedios cuando importan)
Problema: triggers de tabla Oracle (BEFORE INSERT, AFTER UPDATE) frecuentemente contienen lógica de negocio crítica.
Manifestación:
CREATE OR REPLACE TRIGGER TRG_PEDIDOS_TOTAL
BEFORE INSERT OR UPDATE ON T_PEDIDOS
FOR EACH ROW
BEGIN
-- Recalcula total sumando T_PEDIDO_DETALLE
SELECT SUM(cantidad * precio_unit)
INTO :NEW.total
FROM T_PEDIDO_DETALLE
WHERE pedido_id = :NEW.id;
END;Trampa: si la migración elimina el trigger sin replicar la lógica en service, los totales no se recalculan.
Solución:
- Decisión consciente en ADR sobre cada trigger: mantener en BD o migrar a service
- Si se migra: tests de regresión que validen el cálculo
- Si se mantiene: documentar que el sistema nuevo depende del trigger BD
Problema: Forms tiene LOVs (dropdowns con datos) configurados con queries SQL inline o record groups. Un sistema típico tiene 100-500 LOVs.
Manifestación:
-- LOV en Forms
SELECT codigo, descripcion
FROM T_TIPOS_DOCUMENTO
WHERE activo = 'S'
ORDER BY descripcionTrampa: reescribir cada LOV como un endpoint REST específico genera explosión de endpoints.
Solución: patrón genérico LookupController + LookupService:
GET /api/lookup/tipos_documento → [{code, label}, ...]
GET /api/lookup/paises → [{code, label}, ...]
GET /api/lookup/{tipo}?query=...
El service mapea cada tipo a su query SQL. Una vez implementado, agregar un nuevo LOV es agregar una entrada al switch del service.
Problema: Forms permite invocar otro form con CALL_FORM('F_DETALLE') manteniendo un stack de llamadas. El form llamador se "pausa" hasta que el llamado retorna.
Manifestación: flujos típicos de "doble click en lista → ventana de detalle → retorna a lista".
Trampa: modelo de stack de llamadas no existe en web. Migrar como modales o navegación SPA tiene UX distinto.
Solución:
- Para SPA: navegación con
navigate(...)y state en URL - Para Thymeleaf: redirects con session-stored state
- Documentar cambio de UX para usuarios
Problema: formularios de Forms típicamente tienen 30-60 campos por pantalla en una densidad alta.
Manifestación: un mantenimiento de clientes muestra cédula, nombre, apellidos, dirección 1, dirección 2, teléfono, celular, email, fecha nacimiento, género, estado civil, profesión, ingresos, ... todo visible.
Trampa: rediseñar a "diseño moderno con espacios generosos" puede ser rechazado por usuarios acostumbrados a la densidad. Pierden context-at-a-glance.
Solución: UX research con usuarios. Mantener densidad alta para pantallas operativas, espacios generosos solo para pantallas exploratorias.
Problema: Reports .rdf también son binarios. Tienen queries SQL, layouts, parameters, formatos de output.
Trampa:
- Oracle Reports tiene end-of-support
- Reescribir reports complejos en JasperReports puede ser proyecto propio
- Layouts complejos (subreports, gráficos, master-detail) no son triviales
Solución:
- Inventario completo de reports en assessment
- Decisión por report: JasperReports / BI Publisher / Power BI / reescritura como dashboard
- Pilotear con el report más complejo
Problema: Forms 11g/12c se ejecuta en cliente como Java Applet (deprecated) o Java Web Start (deprecated). Browsers modernos no soportan applets.
Manifestación: clientes usan IE 11 o navegadores parchados ad-hoc para que el applet funcione.
Trampa: el cliente legacy probablemente ya está degradado. Los usuarios pueden estar usando versiones específicas de Java cliente.
Solución: la migración a web nativo resuelve esto, pero validar que los usuarios pueden actualizar su navegador.
Problema: Forms típicamente usa roles Oracle DB para permisos (usuarios y roles definidos en Oracle, asignados con GRANT).
Manifestación:
GRANT SELECT, INSERT, UPDATE ON T_CLIENTES TO ROL_ADMIN;
GRANT SELECT ON T_CLIENTES TO ROL_CONSULTA;Trampa: en stack web, autenticación es de aplicación (Spring Security con usuarios en BD propia o LDAP). Permisos en @PreAuthorize o filters. Modelos NO son 1:1.
Solución:
- Decisión en ADR sobre modelo de permisos
- Migrar usuarios Oracle a tabla
T_USERSy roles aT_ROLES - O integrar con LDAP/AD si existe en la organización
Problema: schemas Oracle de 15-20 años suelen tener inconsistencias (tablas T_* y TBL_*, columnas mayúsculas y minúsculas, FKs con nombres inconsistentes).
Trampa: migrar tal cual mantiene la deuda. Limpiar requiere migración de datos.
Solución:
- Decisión consciente: mantener schema actual (más simple, menos valor) o limpiar (más caro, más valor)
- Si se limpia: documentar mapping legacy ↔ nuevo, considerar herramienta de sync durante transición
Problema: auditoría en Oracle se implementa típicamente con PRAGMA AUTONOMOUS_TRANSACTION para no afectar la transacción principal.
Manifestación:
CREATE OR REPLACE TRIGGER TRG_AUDIT_CLIENTES
AFTER INSERT OR UPDATE OR DELETE ON T_CLIENTES
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO T_AUDIT_LOG (...) VALUES (...);
COMMIT;
END;Trampa: en Java equivalente con Spring AOP @AuditLog, si la transacción principal falla y se hace rollback, el log de auditoría TAMBIÉN se hace rollback (a menos que se use @Transactional(propagation = REQUIRES_NEW)).
Solución:
- Si se mantiene en BD: trigger sigue funcionando
- Si se migra a Java: usar
REQUIRES_NEWexplícitamente para preservar comportamiento de transacción autónoma
Problema: Forms usa features Oracle-specific (sequences, packages, types). Si la migración incluye también cambio de BD (Oracle → PostgreSQL), TODO el código PL/SQL debe migrarse.
Trampa: mezclar dos migraciones en un proyecto multiplica el riesgo. Cada migración es proyecto propio.
Solución: mantener Oracle DB en MVP de migración Forms→Java. Migrar BD como proyecto separado después si el cliente lo decide.
Problema: Premier Support de Oracle Fusion Middleware 12c termina diciembre 2026. Extended Support hasta diciembre 2027.
Trampa: clientes que postergaron migración 5+ años ahora tienen presión real, no marketing.
Solución: documentar timeline en propuesta. Después de EOS, no hay parches de seguridad ni bug fixes. Cliente debe decidir antes de Q4 2026.