Skip to main content

Command Palette

Search for a command to run...

📘 APEX HTML Report Builder

Published
10 min read
📘 APEX HTML Report Builder

Cómo crear reportes HTML dinámicos y descargarlos sin sufrir en el intento

Por Gerardo Jaen · Nov 14, 2025 · 8 min read

Si alguna vez intentaste:

  • crear una vista HTML personalizada en APEX,

  • previsualizar código dinámico,

  • generar reportes modernos,

  • exportarlos a Excel o PDF,

  • o peor aún… intentar usar los reportes nativos de APEX,

…entonces ya sabes que:

❌ No puedes ver el HTML correctamente.
❌ El Classic Report es rígido.
❌ Las plantillas de Interactive Report te limitan.
❌ Exportar a Excel nativo rompe el estilo.
❌ Exportar a PDF requiere instalar impresoras remotas o BI Publisher.
❌ APEX 24.x actualizó cosas y rompió otras.
❌ Todo es más complicado de lo que debería ser.

Sí. Lo vivimos todos.
Por eso nació este tutorial.

Aquí aprenderás la forma más práctica y moderna de crear:

✔ HTML dinámico
✔ Vista previa en tiempo real
✔ Reportes con tu propio estilo
✔ Exportación a Excel, Word y PDF
✔ Sin pelear con plantillas de APEX
✔ Sin depender del motor PDF de APEX
✔ Sin licencias adicionales
✔ Sin hacks

Enlace con ejemplos

Vamos paso por paso.

😎 PARTE 1 — Crear un editor HTML con vista previa y exportación

La meta es simple:

➡ Tener una región donde escribas HTML
➡ Verlo exactamente como se renderiza
➡ Descargarlo como Excel, Word y PDF
➡ Sin que APEX modifique nada

Para eso usamos:

  • CodeMirror (editor de código con resaltado)

  • XLSX.js (Excel)

  • html2pdf.js (PDF)

  • APEX Universal Theme para mantener estilo moderno

🔧 PASO 1 — Agrega estas librerías (CDN)

<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js"></script>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/htmlmixed/htmlmixed.min.js"></script>

¿Por qué usar CDN y no Static Files?

Porque:

✔ No te peleás con MIME types
✔ No dependés del servidor
✔ Funciona en cualquier ambiente
✔ Más rápido y sin configuraciones raras

(Opcional: podés colocar las librerías en Shared Components → User Interface para tenerlas siempre disponibles.)

🔧 PASO 2 — Crear la región completa (Editor + Vista previa + Exportar)

Esta región te permite:

  • Escribir HTML

  • Verlo renderizado

  • Exportarlo como Excel, Word o PDF

  • Todo en una misma pantalla

Código completo de la región

<!-- ============================
     LIBRERÍAS DESDE CDN (FUNCIONAN)
=============================== -->

<!-- XLSX -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>

<!-- html2pdf -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js"></script>

<!-- CodeMirror -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/htmlmixed/htmlmixed.min.js"></script>
<style>
    .nps-container { width:100%; }

    .CodeMirror {
        border:1px solid #ccc;
        height:260px;
        font-size:15px;
        border-radius:6px;
    }

    .nps-buttons {
        display:flex;
        flex-wrap:wrap;
        gap:10px;
        margin:15px 0;
    }

    .nps-buttons button {
        flex:1;
        min-width:160px;
        padding:10px;
        border-radius:8px;
        font-size:15px;
        display:flex;
        justify-content:center;
        align-items:center;
        gap:6px;
        transition:.2s;
        box-shadow:0 2px 6px rgba(0,0,0,.15);
    }

    .nps-buttons button:hover {
        transform:scale(1.03);
        box-shadow:0 4px 10px rgba(0,0,0,.25);
    }

    #previewArea {
        width:100%;
        min-height:120px;
        overflow-x:auto;
        padding:15px;
        border:1px solid #ccc;
        border-radius:6px;
    }

    /* DARK MODE */
    html.u-ColorScheme--dark .CodeMirror {
        background:#1e1e1e;
        color:#fff;
        border-color:#555;
    }

    html.u-ColorScheme--dark #previewArea {
        background:#2b2b2b;
        color:#fff;
        border-color:#555;
    }
</style>

<div class="nps-container">

    <h3>Editor HTML</h3>

    <textarea id="editorCodigo">
<table border="1">
<tr><th>Hola</th></tr>
<tr><td>Mundo</td></tr>
</table>
    </textarea>

    <div class="nps-buttons">
        <button type="button" class="t-Button t-Button--hot"
                onclick="generarVista()">
            <span class="fa fa-eye"></span> Generar Vista
        </button>

        <button type="button" class="t-Button t-Button--primary"
                onclick="exportarExcel()">
            <span class="fa fa-file-excel-o"></span> Excel
        </button>

        <button type="button" class="t-Button t-Button--primary"
                onclick="exportarWord()">
            <span class="fa fa-file-word-o"></span> Word
        </button>

        <button type="button" class="t-Button t-Button--primary"
                onclick="exportarPDF()">
            <span class="fa fa-file-pdf-o"></span> PDF
        </button>
    </div>

    <h3>Vista Previa</h3>

    <div id="previewArea">
        <b>La vista previa aparecerá aquí...</b>
    </div>
</div>

<script>
let editor;

// Inicializar CodeMirror
document.addEventListener("DOMContentLoaded", function () {
    editor = CodeMirror.fromTextArea(
        document.getElementById("editorCodigo"),
        {
            mode: "htmlmixed",
            lineNumbers: true,
            theme: "default"
        }
    );
});

function generarVista() {
    const code = editor.getValue();
    document.getElementById("previewArea").innerHTML = code;
    apex.message.showPageSuccess("Vista generada.");
}

function exportarExcel() {
    const html = document.getElementById("previewArea").innerHTML;
    if (!html.trim()) return apex.message.alert("No hay tabla.");

    const temp = document.createElement("div");
    temp.innerHTML = html;

    const tabla = temp.querySelector("table");
    if (!tabla) return apex.message.alert("Debe haber una tabla.");

    const wb = XLSX.utils.table_to_book(tabla);
    XLSX.writeFile(wb, "archivo.xlsx");
}

function exportarWord() {
    const contenido = document.getElementById("previewArea").innerHTML;
    if (!contenido.trim()) return apex.message.alert("Nada que exportar.");

    const blob = new Blob(
        ['<html><body>' + contenido + '</body></html>'],
        { type: "application/msword" }
    );

    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = "archivo.doc";
    link.click();
}

function exportarPDF() {
    const element = document.getElementById("previewArea");
    if (!element.innerHTML.trim()) return apex.message.alert("Nada que exportar.");

    html2pdf().from(element).save("archivo.pdf");
}
</script>

✔ Moderno
✔ Fácil de copiar
✔ Funciona en cualquier app APEX

🤯 PARTE 2 — Crear un reporte dinámico desde una colección o tabla real

Aquí es donde APEX suele complicar la vida.

Si quieres un reporte bonito y flexible, APEX te da:

  • Classic Report 😕

  • Interactive Report 😵

  • IR/IG con demasiadas opciones

  • Export PDF que pierde estilos

  • BI Publisher (caro, pesado, innecesario)

La pregunta de siempre:

¿No hay una forma más directa?

Sí: crear tu propio HTML dinámico.

🔧 PASO 1 — Crear la colección (o usar tu tabla real)

Antes de construir el reporte, necesitamos una fuente de datos.
Para este tutorial elegimos una colección APEX, porque:

  • no requiere crear tablas,

  • es perfecta para ejemplos,

  • permite insertar datos rápidamente,

  • y se comporta igual que una tabla real en una consulta SQL.

Pero es importante aclarar algo:
No estás obligado a usar colecciones.
Si ya tenés una tabla en tu base de datos, simplemente cambiás la consulta y listo.
El método funciona exactamente igual.

Aquí dejamos una colección llamada COL_REPORTE con cuatro registros de ejemplo:

BEGIN
    -- Si existe, la borro
    IF APEX_COLLECTION.COLLECTION_EXISTS('COL_REPORTE') THEN
        APEX_COLLECTION.DELETE_COLLECTION('COL_REPORTE');
    END IF;

    -- Crear colección
    APEX_COLLECTION.CREATE_COLLECTION('COL_REPORTE');

    -- Insertar 4 filas de ejemplo
    APEX_COLLECTION.ADD_MEMBER(
        p_collection_name => 'COL_REPORTE',
        p_c001 => '1',
        p_c002 => 'Gerardo',
        p_c003 => 'Costa Rica',
        p_c004 => 'Programador'
    );

    APEX_COLLECTION.ADD_MEMBER(
        p_collection_name => 'COL_REPORTE',
        p_c001 => '2',
        p_c002 => 'Ana',
        p_c003 => 'México',
        p_c004 => 'Analista'
    );

    APEX_COLLECTION.ADD_MEMBER(
        p_collection_name => 'COL_REPORTE',
        p_c001 => '3',
        p_c002 => 'Luis',
        p_c003 => 'Colombia',
        p_c004 => 'DBA'
    );

    APEX_COLLECTION.ADD_MEMBER(
        p_collection_name => 'COL_REPORTE',
        p_c001 => '4',
        p_c002 => 'Sofía',
        p_c003 => 'Argentina',
        p_c004 => 'Desarrolladora'
    );
END;

✔ Eso es todo.
✔ Colección lista.
✔ Datos simulados.
✔ Sin necesidad de crear tablas ni modelos.

Y recuerda:
Si tienes una tabla real, tan solo cambias la fuente de datos.
El reporte funciona exactamente igual.

🔧 ¿Por qué este método es “la forma inteligente” de hacer reportes en APEX?

Porque con un reporte HTML dinámico ganas:

✔ Control total del diseño
✔ Control total del CSS
✔ Control total de las columnas
✔ Control de los colores, fuentes, bordes
✔ Control del orden y formato
✔ Exportaciones que respetan tu estilo
✔ No dependes del motor PDF de APEX
✔ No necesitas licencias adicionales
✔ Funciona igual en cualquier versión de APEX
✔ Puedes insertar imágenes, logos, badges, íconos…

Y lo mejor:

Lo que ves en pantalla es EXACTAMENTE lo que se exporta
a Excel y a PDF.

Ningún otro componente nativo de APEX te da esa garantía.


😎 PASO 2 — Construir el reporte HTML dinámico

Aquí está el PL/SQL que genera la tabla con estilo moderno:

Usar una región PL/SQL dinámica te da la libertad que APEX normalmente no te da:

✔ puedes generar el diseño que quieras,
✔ con los estilos que quieras,
✔ usando datos reales o colecciones,
✔ y exportarlo tal cual se ve.

Aquí tienes una versión moderna, limpia y basada en Universal Theme:

DECLARE
    CURSOR c_cols IS
        SELECT 'ID' AS nombre, 'NUMBER' AS tipo FROM dual
        UNION ALL SELECT 'Nombre', 'VARCHAR2' FROM dual
        UNION ALL SELECT 'País', 'VARCHAR2' FROM dual
        UNION ALL SELECT 'Puesto', 'VARCHAR2' FROM dual;

    CURSOR c_data IS
        SELECT c001, c002, c003, c004
          FROM apex_collections
         WHERE collection_name = 'COL_REPORTE';

    v_html CLOB := '';
BEGIN
    v_html := v_html || '<div id="tablaPreview" style="margin-top:15px;">';

    v_html := v_html || '
    <table class="t-Report-report ut-table" 
           style="width:100%;border-collapse:collapse;font-size:14px;">
    ';

    v_html := v_html || '<thead><tr style="background:#f4f4f4;">';

    FOR c IN c_cols LOOP
        v_html := v_html || '<th style="
            padding:10px;
            border-bottom:2px solid #ddd;
            text-align:center;
            font-weight:bold;
        ">' || c.nombre || '</th>';
    END LOOP;

    v_html := v_html || '</tr></thead>';
    v_html := v_html || '<tbody>';

    FOR r IN c_data LOOP        
        v_html := v_html || '<tr>';

        v_html := v_html || '<td style="padding:8px;text-align:center;">'||r.c001||'</td>';
        v_html := v_html || '<td style="padding:8px;text-align:center;">'||r.c002||'</td>';
        v_html := v_html || '<td style="padding:8px;text-align:center;">'||r.c003||'</td>';
        v_html := v_html || '<td style="padding:8px;text-align:center;">'||r.c004||'</td>';

        v_html := v_html || '</tr>';
    END LOOP;

    v_html := v_html || '</tbody></table></div>';

    RETURN v_html;
END;

🔥 ¿Por qué funciona tan bien?

Porque:

  • No dependes del motor HTML interno de APEX.

  • No dependes de cómo APEX “interpreta” tus columnas.

  • No dependes de plantillas fijas.

  • No dependes de IR/IG (los destructores del rendimiento).

  • No dependes del motor PDF nativo.

Es tu HTML, tu CSS, tu reporte.
Y APEX solo lo muestra, no opina.

¿Por qué usamos v_html := v_html ||?

Lo explico simple:

En PL/SQL no podés escribir HTML como en un archivo normal.
Por eso vas almacenando tu HTML en una variable, por partes:

v_html := v_html || '<table>';
v_html := v_html || '<tr><td>Hola</td></tr>';

Cada || es como agregar otro bloque LEGO.

Al final hacés:

RETURN v_html;

Y APEX lo muestra como HTML real.


🔧 PASO 3 — Agregar botones modernos para exportar el reporte

Aquí viene la parte más gratificante:

👉 Exportar tu reporte dinámico a Excel
👉 Exportarlo a PDF
👉 Sin BI Publisher
👉 Sin plantillas complicadas
👉 Sin refresh de página
👉 Sin perder el estilo

Todo usando 2 librerías modernas:

  • XLSX.js (para Excel)

  • html2pdf.js (para PDF)


📦 Botones hermosos y funcionales

Pon esto en una Región Estática (o encima de tu tabla):

<div style="margin-bottom:15px;">
    <button type="button"
            onclick="descargarExcelDesdeTabla()"
            class="t-Button t-Button--primary t-Button--padMedium"
            style="margin-right:10px;">
        <span class="fa fa-file-excel-o"></span> Descargar Excel
    </button>

    <button type="button"
            onclick="descargarPDFTabla()"
            class="t-Button t-Button--hot t-Button--padMedium">
        <span class="fa fa-file-pdf-o"></span> Descargar PDF
    </button>
</div>

📘 Función para exportar Excel

<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>

<script>
function descargarExcelDesdeTabla() {
  const tabla = document.querySelector("#tablaPreview table");

  if (!tabla) {
    apex.message.alert("No se encontró la tabla para exportar.");
    return;
  }

  const wb = XLSX.utils.table_to_book(tabla, { sheet: "Reporte" });
  XLSX.writeFile(wb, "Reporte_Personas.xlsx");
}
</script>

📕 Función para exportar PDF

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js"></script>

<script>
function descargarPDFTabla() {
  const tabla = document.getElementById("tablaPreview");

  if (!tabla) {
    apex.message.alert("No se encontró la tabla.");
    return;
  }

  html2pdf().from(tabla).save("Reporte_Personas.pdf");
}
</script>

De esta manera me quedo la pantalla.


🎯 Conclusión — ¿Por qué este método es mejor? (Ventajas reales)

Después de probar reportes nativos, plantillas complicadas y motores PDF limitados, esta técnica demuestra ser la forma más práctica de trabajar en Oracle APEX.
Estas son las ventajas reales, las que de verdad importan al desarrollar:


1. Control total del diseño

Podés definir:

  • colores

  • bordes

  • fuentes

  • tamaños

  • logos

  • estilos modernos

Tu reporte se ve exactamente como vos querés.
Sin pelear con plantillas rígidas de APEX.


2. Lo que ves es lo que exportás (WYSIWYG real)

La vista previa y las exportaciones (Excel, Word, PDF) conservan:

✔ estilos
✔ formato
✔ alineaciones
✔ colores

APEX normalmente no garantiza esto.
Con tu propio HTML, sí.


3. Funciona en cualquier APEX (sin importar versión)

No depende del motor nativo de:

  • PDF

  • IR/IG

  • Classic Report

Funciona igual en APEX 20.x, 21.x, 22.x, 23.x y 24.x.
Nada se rompe cuando actualizan el ambiente.


4. Exportación moderna sin BI Publisher

No necesitás:

❌ impresoras remotas
❌ licencias adicionales
❌ plantillas RTF
❌ servidores externos

Solo XLSX.js y html2pdf.js, listas para usar.


5. Rápido, ligero y sin recargar la página

Las exportaciones:

  • no generan submits

  • no refrescan la página

  • no llaman procesos del servidor

  • no consumen memoria en APEX

Todo ocurre en el navegador, al instante.


6. Compatible con cualquier fuente de datos

Podés usar:

  • APEX Collection

  • tablas reales

  • vistas

  • JSON

  • Web Services

  • cualquier consulta SQL

Mientras puedas generar HTML, funciona.


7. Evita las limitaciones de IR e IG

No sufrís con:

❌ filtros automáticos
❌ paginaciones forzadas
❌ columnas que APEX decide ocultar
❌ estilos que se pierden
❌ render HTML restringido

Tus datos se muestran limpios y modernos, sin ruido.


8. Fácil de mantener

Todo está en:

  • una región PL/SQL

  • una región estática con JS/CSS

  • librerías limpias por CDN

Sin plantillas escondidas, sin configuraciones ocultas.


9. Se adapta a cualquier diseño corporativo

Podés aplicar:

  • Redwood

  • UT21

  • UT22

  • Dark Mode

  • Branding personalizado

Tu HTML respeta el look & feel que el negocio necesite.


10. 100% reutilizable

Podés replicar este “HTML Report Builder” en:

  • cualquier página

  • cualquier aplicación

  • cualquier proyecto

Solo copiás la región y listo.

Es, simplemente:

La forma más amigable, libre y potente de generar reportes personalizados en Oracle APEX.

More from this blog

Oracle apex en español

5 posts

Oracle APEX en español: guías, soluciones, tutoriales y buenas prácticas para aprender y mejorar en el desarrollo con esta plataforma low-code