Skip to main content
The utb_cep_programs flow manages enrollment in Continuing Education Programs (Educación Continua). It renders a custom product landing page, validates student and graduate identity against the Banner ERP, applies role-based discounts in real time, and writes structured order metadata to WooCommerce.

Flow overview

PropertyValue
Flow IDutb_cep_programs
ClassUTB\ProductBuilder\Flows\CEPFlow
Shortcode[utb_cep_form]
Icondashicons-welcome-learn-more
AJAX endpointscep_calculate_discount, utb_cep_price
Order hookwoocommerce_checkout_order_processedlog_inscription

Shortcode

Place [utb_cep_form] on any page or in a WooCommerce product description to render the standalone enrollment form. On a product page the flow renders the form automatically via render_custom_content().

AJAX endpoints

cep_calculate_discount

Validates the applicant’s identity and calculates their discount in one round-trip. Requires nonce utb_cep_nonce. POST parameters:
ParameterTypeDescription
noncestringWordPress nonce (utb_cep_nonce)
programa_codigostringProgram code from the selector
documentointID document number
primer_nombrestringFirst name (must match Banner)
segundo_nombrestringSecond name (optional, validated if provided)
primer_apellidostringFirst surname (must match Banner)
segundo_apellidostringSecond surname (optional, validated if provided)
Response (success):
{
  "periodo": "2026.1",
  "concepto": "EP-PROG01",
  "programa_codigo": "PROG01",
  "precio": 1500000,
  "precio_con_descuento": 1275000,
  "descuento_porcentaje": 15,
  "descuento_monto": 225000,
  "rol_detectado": "ESTUDIANTE",
  "rol_especifico": "ESTUDIANTE-UG",
  "descripcion": "Descuento estudiante UTB",
  "request_id": "cep_1740000000_1234"
}
Results are cached for 15 minutes per (documento, programa_codigo, periodo, names_hash) tuple. A 30-second per-document rate limit applies after each live API call.

utb_cep_price

Returns a WooCommerce-formatted price string for visual updates when the user changes program selection. POST parameters:
ParameterTypeDescription
pricefloatRaw price value
Response (success):
{
  "html": "<span class=\"woocommerce-Price-amount\">$1.500.000</span>",
  "price": 1500000
}
This endpoint does not require a nonce. It only formats a price provided by the client and performs no privileged operations.

Student role validation

Role validation is handled by CEPRolesValidator. When the applicant submits their document number, the class:
1

Check transient cache

Looks for cep_roles_{documento} in the WordPress transient cache (10-minute TTL). Returns cached data immediately if found.
2

Apply rate limit

If no cache exists, checks the cep_rate_{documento} transient (30-second TTL). Returns a rate_limit WP_Error if the limit is active, preventing API flooding.
3

Obtain OAuth token

Calls ApiConnectionManager('cep_integrator')->get_auth_headers() to retrieve a valid Bearer token for the Banner integration.
4

Query Banner API

Issues a GET request to {api_base}/woocommerce/rol?documento={documento} with a 25-second timeout. Expects JSON with uppercase keys IDENTIFICACION, NOMBRE, APELLIDOS, and ROLES.
5

Normalize roles

Maps raw role strings to uppercase hyphenated format (e.g., ESTUDIANTE_UGESTUDIANTE-UG). Determines rol_principal as ESTUDIANTE or EGRESADO for UI display.
6

Cache and return

Stores the normalized result for 10 minutes and activates the 30-second rate limit key.
Expected Banner API response structure:
{
  "IDENTIFICACION": "12345678",
  "NOMBRE": "JUAN CARLOS",
  "APELLIDOS": "PEREZ GOMEZ",
  "ROLES": ["ESTUDIANTE_UG", "MONITOR"]
}
The Banner API returns uppercase keys (NOMBRE, APELLIDOS). If the ERP changes the casing, identity validation will silently fail. Check CEPRolesValidator.php line 97 if users report unexpected “document not found” errors.

Identity fraud prevention

Before applying any discount, the flow performs a bilateral name-and-document validation inside ajax_calculate_discount. The names the applicant typed are compared against what Banner returned for their document number:
  • primer_nombre (typed) must appear in Banner’s NOMBRE.
  • primer_apellido (typed) must appear in Banner’s APELLIDOS.
  • If segundo_nombre or segundo_apellido were typed, they must also appear in Banner’s corresponding field.
All comparisons are case-insensitive, accent-stripped (remove_accents()), and use strpos() substring matching. A mismatch returns error code identity_mismatch and blocks the discount calculation:
Los nombres y/o apellidos ingresados no coinciden con los registrados en la Universidad para este documento.

Discount tiers

Discount calculation is delegated to CEPDiscountCalculator. Once roles are confirmed the class:
  1. Resolves the program’s billing concept code (concepto). If the program has a sku in wp_utb_cep_programs, that SKU is used directly; otherwise the fallback is EP-{CODIGO_PROGRAMA}.
  2. Calls {api_base}/woocommerce/programas/precios?periodo={period}&concepto={concepto}&programa={codigo}.
  3. Reads VALOR_LISTA (base price) and DESCUENTOS array from the response.
  4. Filters discounts to those whose ROL matches one of the applicant’s normalized roles.
  5. Selects the discount with the highest VALOR percentage.
Typical discount values (configured in Banner):
RoleDiscount
ESTUDIANTE (any sub-role)15%
EGRESADO10%
The current academic period is read from UTB_PB_DB_Schema::get_config()['rules']['cep_current_period'] with a fallback to the constant CEP_PERIODO_ACTUAL.

Dynamic program pricing

When the user selects a program from the <select id="cep_programa"> dropdown, cep-flow.js reads the data-precio attribute on the selected option and fires an AJAX call to utb_cep_price to render the formatted price string.
The price label uses direct inline styles (attr('style', '...')), not jQuery .slideDown(), to avoid CSS animation conflicts that previously caused the price display to remain invisible. See changelog entry for v2.4.2.5.

Default form configuration

CEPFlow::get_default_config() returns the field schema used when no JSON configuration has been saved via the Form Builder. The configuration is a flat array of field descriptors:
[
  { "id": "notice_validacion_importante", "type": "notice", "style": "info", "position": "before_form" },
  { "id": "section_datos_personales", "type": "heading", "label": "Datos Personales" },
  { "id": "cep_primer_nombre",   "name": "cep_primer_nombre",   "type": "text",   "required": true },
  { "id": "cep_segundo_nombre",  "name": "cep_segundo_nombre",  "type": "text",   "required": false },
  { "id": "cep_primer_apellido", "name": "cep_primer_apellido", "type": "text",   "required": true },
  { "id": "cep_segundo_apellido","name": "cep_segundo_apellido","type": "text",   "required": false },
  { "id": "cep_tipo_documento",  "name": "cep_tipo_documento",  "type": "select", "required": true,
    "options": { "cc": "Cédula de Ciudadanía", "ce": "Cédula de Extranjería", "ti": "Tarjeta de Identidad", "pasaporte": "Pasaporte" }
  },
  { "id": "cep_documento", "name": "cep_documento", "type": "text", "required": true },
  { "id": "notice_datos_certificacion", "type": "notice", "style": "info", "position": "inline" },
  { "id": "section_programa", "type": "heading", "label": "Programa Académico" },
  { "id": "cep_programa", "name": "cep_programa", "type": "system_program_selector", "required": true },
  { "id": "cep_documento_file", "name": "cep_documento_file", "type": "file", "required": true,
    "accept": ".pdf,.jpg,.jpeg,.png,.doc,.docx", "max_size": 2048 }
]
FormConfigManager::get_config() loads the configuration in this priority order:
  1. _utb_form_config post meta on the WooCommerce product (JSON saved by the Form Builder).
  2. _utb_linked_cert_id post meta → form_config_json from wp_utb_certificates.
  3. Flow’s get_default_config() result.

Cart metadata fields

The following order item meta keys are written by prepare_cart_metadata() and are visible in WooCommerce → Orders → (order) → Order details:
Meta keySource
_utb_cep_primer_nombrecep_primer_nombre (form field)
_utb_cep_segundo_nombrecep_segundo_nombre
_utb_cep_primer_apellidocep_primer_apellido
_utb_cep_segundo_apellidocep_segundo_apellido
_utb_cep_tipo_documentocep_tipo_documento
_utb_cep_documentocep_documento
_utb_cep_programa_codigoProgram code from wp_utb_cep_programs
_utb_cep_programa_nombreProgram name from wp_utb_cep_programs
_utb_cep_precioBase program price
_utb_cep_descuento_porcentajeDiscount percentage (if applied)
_utb_cep_descuento_montoDiscount amount in COP (if applied)
_utb_cep_precio_con_descuentoFinal price after discount (if applied)
_utb_cep_rol_aplicadoNormalized role string (e.g. ESTUDIANTE)
_utb_cep_periodoAcademic period (e.g. 2026.1)
_utb_cep_conceptoBilling concept code
_utb_cep_discount_request_idTraceability ID from the discount API call

Dynamic price at checkout

calculate_dynamic_price() overrides the WooCommerce cart item price. It returns _utb_cep_precio_con_descuento when a discount was applied, or _utb_cep_precio as the fallback. If neither key is present, null is returned and the original product price is kept.

Order logging

The log_inscription method is registered on woocommerce_checkout_order_processed with priority 10. It writes the completed enrollment record to wp_utb_cep_inscriptions via CEPInscriptionsRepository.

Frontend assets

HandleFileDepends on
utb-cep-flow (CSS)assets/frontend/cep-flow.css
utb-cep-debug (JS)assets/frontend/cep-debug.jsjquery
utb-cep-flow (JS)assets/frontend/cep-flow.jsjquery, utb-cep-debug
The JS object UTB_CEP is localized with ajax_url, nonce, and debug_allowed. debug_allowed is true only for users with manage_options or edit_posts capabilities.

Debug mode

Add #debug or ?debug=1 to the product URL to enable verbose AJAX logging in the browser console. This requires the current WordPress user to have the Administrator or Editor role; anonymous users are silently blocked. The global window.UTB_DEBUG.enable() method can also be called from the browser console when logged in.

Build docs developers (and LLMs) love