Q-Expert · Casos reales · Q-Expert DistributorsRead in English
Episodio6

Listo para la app

Manual paso a paso

Las capturas muestran Q-Expert en inglés; la interfaz en español llegará pronto.

01La pregunta de negocio

Las tablas limpias ya existen; ahora la app necesita un modelo confiable. Un esquema estrella con un hecho para los movimientos, otro para presupuesto contra real, y una dimensión por cliente, producto, sucursal, vendedor y fecha, guardado como QVD en la carpeta del modelo, con solo el modelo en memoria.

02Antes de empezar

03Paso a paso

  1. 1

    Revisa antes de escribir el modelo

    Tres nodos:

    • Un LET/SET: LET vMovementRows = NoOfRows('Movement').
    • Un IF sobre $(vMovementRows) = 0: THEN escribe un TRACE y sale del script; ELSE escribe el número de filas.
    • Un SWITCH sobre '$(vEnvironment)': un TRACE para PROD, otro para QA y uno por defecto.
    Revisa antes de escribir el modelo
    Figura 6.1 — Revisa antes de escribir el modelo
  2. 2

    Arma las llaves

    Un TXF tmp_FactSales (NoConcatenate) lee Movement. Con el Key Builder:

    • MovementDate como %SK_Date
    • CustomerID como %SK_Customer
    • ProductID como %SK_Product
    • BranchID como %SK_Branch
    • SalespersonID como %SK_Salesperson
    • AutoNumber(BranchID & '|' & Num(Month(MovementDate))) como %CK_BranchMonth
    Arma las llaves
    Figura 6.2 — Arma las llaves
  3. 3

    Un calendario maestro

    El Master Calendar Wizard lo arma sobre Movement.MovementDate:

    • El rango de fechas en vMinDate y vMaxDate.
    • tmp_CalendarBase con AUTOGENERATE … WHILE.
    • Dim_Calendar con %SK_Date, Year, MonthNum, Month, Quarter, Week, Weekday e IsWeekend.
    • IsHoliday y HolidayName desde map_Holiday.
    Un calendario maestro
    Figura 6.3 — Un calendario maestro
  4. 4

    La capa de negocio, guardada como QVD

    Nodos TXF para el modelo, cada uno con un CHECKPOINT que lo guarda en $(vPathModel):

    • Fact_Sales
    • Fact_BudgetVsActual: antes de guardarlo, un nodo RENAME, campo BudgetAmount a BudgetAmountCOP
    • Dim_Customer, Dim_Product, Dim_Branch, Dim_Salesperson
    • Dim_Calendar
    La capa de negocio, guardada como QVD
    Figura 6.4 — La capa de negocio, guardada como QVD
  5. 5

    Deja que Qlik derive el calendario de alta

    Un AutoCalendar sobre SignupDate: año, mes y año-trimestre, derivados por Qlik con DECLARE FIELD DEFINITION y DERIVE FIELDS.

    Deja que Qlik derive el calendario de alta
    Figura 6.5 — Deja que Qlik derive el calendario de alta
  6. 6

    Solo queda el modelo

    Un DROP de todas las tablas de transformación:

    • Customer, Product, Salesperson, Branch
    • Sales, SaleReturn, Movement, BudgetVsActual
    • tmp_FactSales, tmp_CalendarBase

    Un último TRACE dice que la recarga terminó.

    Solo queda el modelo
    Figura 6.6 — Solo queda el modelo
  7. 7

    Apaga el checkpoint de depuración

    En el checkpoint del episodio 5, marca Marked for Toolbar Disable/Enable. Luego usa el interruptor de checkpoints de la barra: todos los checkpoints marcados se apagan de una vez, y su STORE y EXIT SCRIPT quedan en el script como comentarios, listos para volver a encenderse.

    Apaga el checkpoint de depuración
    Figura 6.7 — Apaga el checkpoint de depuración
  8. 8

    Genera, exporta, despliega

    Generate Syntax: _Main define los formatos, vTxfPath, vPathRaw, las variables de proyecto (con HidePrefix) e incluye cada pestaña. Export guarda el script, su documentación o su JSON; Deploy escribe todas las pestañas en una carpeta. El episodio 7 importa todo este modelo de vuelta desde su script.

    Genera, exporta, despliega
    Figura 6.8 — Genera, exporta, despliega

04Revisa tu resultado

_Main

Se pega en Qlik

Este es el único script que pegas en Qlik Sense: abre el Editor de carga de datos de la app, reemplaza su contenido por este y recarga. Cada línea $(MUST_INCLUDE=…) lee en su lugar uno de los archivos de abajo, así el resto del modelo nunca entra al editor.

Ver script · 49 líneas
/*----------------------------------------------------------------------------------------
	Description [MAIN]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

SET ThousandSep=',';
SET DecimalSep='.';
SET MoneyThousandSep=',';
SET MoneyDecimalSep='.';
SET MoneyFormat='$ #,##0.00;-$ #,##0.00';
SET TimeFormat='hh:mm:ss';
SET DateFormat='YYYY-MM-DD';
SET TimestampFormat='YYYY-MM-DD hh:mm:ss';
SET MonthNames='Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec';
SET LongMonthNames='January;February;March;April;May;June;July;August;September;October;November;December';
SET DayNames='Mon;Tue;Wed;Thu;Fri;Sat;Sun';
SET LongDayNames='Monday;Tuesday;Wednesday;Thursday;Friday;Saturday;Sunday';
SET FirstWeekDay=0;
SET BrokenWeeks=1;
SET ReferenceDay=4;
SET FirstMonthOfYear=1;

LET vExecutionDate = Timestamp(Now(), 'YYYY-MM-DD hh:mm:ss');

SET vTxfPath  = 'lib://QXDistributors/Transform/';
SET vPathRaw = 'lib://QXDistributors/';
SET vEnvironment = 'PROD';   // PROD, QA or DEV
SET vBaseCurrency = 'COP';   // Currency every amount is converted to
SET vPathModel = 'lib://QXDistributors/Model/';   // The model QVDs the apps load
SET HidePrefix = '%';   // Keys (%) stay out of the app lists

$(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_1.qvs);
$(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_2.qvs);
$(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_3.qvs);
$(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_4.qvs);
$(MUST_INCLUDE=$(vTxfPath)_QVS/Movement.qvs);
$(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_6.qvs);

Flow_1.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Flow_1 de Generate Syntax, Export descarga Flow_1.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Flow_1.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 35 líneas
/*----------------------------------------------------------------------------------------
	Description [Flow_1]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// Table defined directly in the script (2 columns).
[map_DocType_Src]:
    LOAD * INLINE [
    "DocType", "DocTypeName"
    "NIT", "Company"
    "CC", "Person"
    ];

// -------------------------------------------------------------------------------------------------
// Mapping table (DocType → DocTypeName) from map_DocType_Src, for ApplyMap.
[map_DocType]:
    MAPPING LOAD
        DocType,
        DocTypeName
    RESIDENT [map_DocType_Src];

DROP TABLE [map_DocType_Src];

Flow_2.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Flow_2 de Generate Syntax, Export descarga Flow_2.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Flow_2.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 36 líneas
/*----------------------------------------------------------------------------------------
	Description [Flow_2]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// Table defined directly in the script (2 columns).
[map_CustomerStatus_Src]:
    LOAD * INLINE [
    "Status", "CustomerStatus"
    "A", "Active"
    "I", "Inactive"
    "B", "Blocked"
    ];

// -------------------------------------------------------------------------------------------------
// Mapping table (Status → CustomerStatus) from map_CustomerStatus_Src, for ApplyMap.
[map_CustomerStatus]:
    MAPPING LOAD
        Status,
        CustomerStatus
    RESIDENT [map_CustomerStatus_Src];

DROP TABLE [map_CustomerStatus_Src];

Flow_3.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Flow_3 de Generate Syntax, Export descarga Flow_3.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Flow_3.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 36 líneas
/*----------------------------------------------------------------------------------------
	Description [Flow_3]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// Table defined directly in the script (2 columns).
[map_ChannelName_Src]:
    LOAD * INLINE [
    "Channel", "ChannelName"
    "P", "Phone"
    "T", "In store"
    "W", "Web"
    ];

// -------------------------------------------------------------------------------------------------
// Mapping table (Channel → ChannelName) from map_ChannelName_Src, for ApplyMap.
[map_ChannelName]:
    MAPPING LOAD
        Channel,
        ChannelName
    RESIDENT [map_ChannelName_Src];

DROP TABLE [map_ChannelName_Src];

Flow_4.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Flow_4 de Generate Syntax, Export descarga Flow_4.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Flow_4.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 24 líneas
/*----------------------------------------------------------------------------------------
	Description [Flow_4]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// Mapping table (BrandID → BrandName) from $(vPathRaw)Brands.txt, for ApplyMap.
[map_BrandName]:
    MAPPING LOAD
        BrandID,
        BrandName
    FROM [$(vPathRaw)Brands.txt] (txt, utf8, delimiter is '|', embedded labels, msq);

Movement.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Movement de Generate Syntax, Export descarga Movement.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Movement.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 735 líneas
/*----------------------------------------------------------------------------------------
	Description [Movement]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// Empty table with 1 column (WHERE 1=0), schema only.
[tmp_SalesRaw]:
    LOAD * INLINE [
    "Dummy"
    ] WHERE 1=0;

// -------------------------------------------------------------------------------------------------
// Loop FOR EACH … IN FileList() over $(vFile).
FOR EACH vFile IN FileList('$(vPathRaw)Sales_*.csv')

    // -------------------------------------------------------------------------------------------------
    // Raw load from $(vFile), concatenated into [tmp_SalesRaw].
    CONCATENATE ([tmp_SalesRaw])
    LOAD
        SaleID,
        SaleDate,
        CustomerID,
        ProductID,
        SalespersonID,
        BranchID,
        Quantity,
        UnitPrice,
        DiscountPct,
        Currency,
        Channel,
        FileName()                                                                       AS SourceFile
    FROM [$(vFile)] (txt, utf8, delimiter is ',', embedded labels, msq);

NEXT vFile;

DROP FIELD Dummy FROM [tmp_SalesRaw];

LET vSalesRawRows = NoOfRows('tmp_SalesRaw');

TRACE Sales rows read from the monthly files: $(vSalesRawRows);

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_Parameter (no source set yet).
[tmp_Parameter]:
    LOAD
        Parameter,
        Value
    FROM [$(vPathRaw)Parameters.txt] (txt, utf8, delimiter is '	', embedded labels, msq);

LET vCreditThreshold = Lookup('Value', 'Parameter', 'CreditThreshold', 'tmp_Parameter');

DROP TABLE [tmp_Parameter];

// -------------------------------------------------------------------------------------------------
// Mapping table (Num(Date#(HolidayDate, 'YYYY-MM-DD')) AS HolidayDate → HolidayName) from $(vPathRaw)Holidays.csv, for ApplyMap.
[map_Holiday]:
    MAPPING LOAD DISTINCT
        Num(Date#(HolidayDate, 'YYYY-MM-DD')) AS HolidayDate,
        HolidayName
    FROM [$(vPathRaw)Holidays.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

QUALIFY *;

UNQUALIFY CustomerID, ProductID, SalespersonID, BranchID, CategoryID, BrandID, DocNumber;

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_CustomerRaw (no source set yet).
[tmp_CustomerRaw]:
    LOAD
        CustomerID,
        CustomerName,
        DocType,
        DocNumber,
        City,
        Segment,
        SignupDate,
        Status,
        CreditLimit
    FROM [$(vPathRaw)Customers.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_ProductRaw (no source set yet).
[tmp_ProductRaw]:
    LOAD
        ProductID,
        SKU,
        ProductName,
        CategoryID,
        BrandID,
        ListPrice,
        UnitCost,
        UnitOfMeasure,
        Status
    FROM [$(vPathRaw)Products.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_SalespersonRaw (no source set yet).
[tmp_SalespersonRaw]:
    LOAD
        SalespersonID,
        SalespersonName,
        BranchID,
        HireDate,
        MonthlyTarget
    FROM [$(vPathRaw)Salespeople.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_BranchRaw (no source set yet).
[tmp_BranchRaw]:
    LOAD
        BranchID,
        BranchName,
        Region,
        City,
        Format
    FROM [$(vPathRaw)Branches.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_CategoryRaw (no source set yet).
[tmp_CategoryRaw]:
    LOAD
        CategoryID,
        CategoryName,
        Family
    FROM [$(vPathRaw)Categories.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

UNQUALIFY *;

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_BrandRaw (no source set yet).
[tmp_BrandRaw]:
    LOAD
        BrandID,
        BrandName,
        CountryOfOrigin
    FROM [$(vPathRaw)Brands.txt] (txt, utf8, delimiter is '|', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Transforms tmp_CustomerRaw.
[tmp_CustomerClean]:
    LOAD
        CustomerID,
        DocNumber,
        [tmp_CustomerRaw.Segment]                                                        AS Segment,
        [tmp_CustomerRaw.CreditLimit]                                                    AS CreditLimit,
        Capitalize(Trim(tmp_CustomerRaw.CustomerName))                                   AS CustomerName,
        ApplyMap('map_DocType', tmp_CustomerRaw.DocType, 'Other')                        AS DocTypeName,
        If(Len(Trim(tmp_CustomerRaw.City)) = 0, 'No city', Trim(tmp_CustomerRaw.City))   AS CustomerCity,
        Date(Date#(tmp_CustomerRaw.SignupDate, 'DD/MM/YYYY'))                            AS SignupDate,
        ApplyMap('map_CustomerStatus', tmp_CustomerRaw.Status, 'Unknown')                AS CustomerStatus,
        If(tmp_CustomerRaw.CreditLimit >= $(vCreditThreshold), 'High', 'Standard')       AS CreditProfile,
        If(DocNumber = Previous(DocNumber), Peek('SurvivorID'), CustomerID)              AS SurvivorID
    RESIDENT [tmp_CustomerRaw]
    ORDER BY DocNumber, CustomerID;

// -------------------------------------------------------------------------------------------------
// Mapping table (CustomerID → SurvivorID) from tmp_CustomerClean, for ApplyMap.
[map_CustomerSurvivor]:
    MAPPING LOAD
        CustomerID,
        SurvivorID
    RESIDENT [tmp_CustomerClean];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_CustomerClean, combined via CONCATENATE.
[Customer]:
    NOCONCATENATE
    LOAD
        CustomerID,
        DocNumber,
        Segment,
        CreditLimit,
        CustomerName,
        DocTypeName,
        CustomerCity,
        SignupDate,
        CustomerStatus,
        CreditProfile
    RESIDENT [tmp_CustomerClean]
    WHERE CustomerID = SurvivorID;

    CONCATENATE ([Customer])
    // -------------------------------------------------------------------------------------------------
    // Table defined directly in the script (3 columns).
    LOAD * INLINE [
    "CustomerID", "CustomerName", "CustomerStatus"
    "-1", "Unknown customer", "Unknown"
    ];

DROP TABLE [tmp_CustomerClean];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_ProductRaw, combined via LEFT JOIN.
[Product]:
    LOAD
        ProductID,
        [tmp_ProductRaw.SKU]                                                             AS SKU,
        CategoryID,
        BrandID,
        [tmp_ProductRaw.ListPrice]                                                       AS ListPrice,
        [tmp_ProductRaw.UnitCost]                                                        AS UnitCost,
        [tmp_ProductRaw.UnitOfMeasure]                                                   AS UnitOfMeasure,
        Capitalize(Trim(tmp_ProductRaw.ProductName))                                     AS ProductName,
        ApplyMap('map_BrandName', BrandID, 'No brand')                                   AS BrandName,
        Round(
            (tmp_ProductRaw.ListPrice - tmp_ProductRaw.UnitCost) / tmp_ProductRaw.ListPrice,
            0.0001
        )                                                                                AS MarginPct
    RESIDENT [tmp_ProductRaw]
    WHERE tmp_ProductRaw.Status = 'ACTIVE';

    LEFT JOIN ([Product])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_CategoryRaw.
    LOAD DISTINCT
        CategoryID,
        [tmp_CategoryRaw.CategoryName]                                                   AS CategoryName,
        [tmp_CategoryRaw.Family]                                                         AS Family
    RESIDENT [tmp_CategoryRaw];

    LEFT JOIN ([Product])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_BrandRaw.
    LOAD
        BrandID,
        CountryOfOrigin
    RESIDENT [tmp_BrandRaw];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_SalespersonRaw, combined via LEFT JOIN.
[Salesperson]:
    LEFT KEEP ([tmp_SalesRaw])
    LOAD
        SalespersonID,
        BranchID                                                                         AS HomeBranchID,
        [tmp_SalespersonRaw.MonthlyTarget]                                               AS MonthlyTarget,
        Capitalize(Trim(tmp_SalespersonRaw.SalespersonName))                             AS SalespersonName,
        Date(Date#(tmp_SalespersonRaw.HireDate, 'YYYY-MM-DD'))                           AS HireDate
    RESIDENT [tmp_SalespersonRaw];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_BranchRaw.
[Branch]:
    LOAD
        BranchID,
        [tmp_BranchRaw.BranchName]                                                       AS BranchName,
        [tmp_BranchRaw.Region]                                                           AS Region,
        [tmp_BranchRaw.City]                                                             AS BranchCity,
        [tmp_BranchRaw.Format]                                                           AS BranchFormat
    RESIDENT [tmp_BranchRaw];

DROP TABLE [tmp_BranchRaw];
DROP TABLE [tmp_BrandRaw];
DROP TABLE [tmp_CategoryRaw];
DROP TABLE [tmp_CustomerRaw];
DROP TABLE [tmp_ProductRaw];
DROP TABLE [tmp_SalespersonRaw];

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_ExchangeRate (no source set yet).
[tmp_ExchangeRate]:
    LOAD
        Currency,
        Rate,
        Date#(ValidFromDate, 'YYYY-MM-DD')                                               AS ValidFromDate,
        Date#(ValidToDate, 'YYYY-MM-DD')                                                 AS ValidToDate
    FROM [$(vPathRaw)ExchangeRates.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Associates SaleDate with the range [ValidFromDate, ValidToDate] from tmp_ExchangeRate.
[tmp_RateBridge]:
    INTERVALMATCH (SaleDate, Currency)
    LOAD
      ValidFromDate,
      ValidToDate,
      Currency
    RESIDENT [tmp_ExchangeRate];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_RateBridge, combined via LEFT JOIN.
[tmp_SaleRate]:
    NOCONCATENATE
    LOAD
        SaleDate,
        ValidFromDate,
        ValidToDate,
        Currency
    RESIDENT [tmp_RateBridge];

    LEFT JOIN ([tmp_SaleRate])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_ExchangeRate.
    LOAD
        Currency,
        Rate,
        ValidFromDate,
        ValidToDate
    RESIDENT [tmp_ExchangeRate];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_SalesRaw, combined via INNER JOIN.
[tmp_Sales]:
    LOAD
        SaleID,
        ProductID,
        SalespersonID,
        BranchID,
        Quantity,
        UnitPrice,
        DiscountPct,
        Currency,
        SourceFile,
        Date(Date#(SaleDate, 'YYYY-MM-DD'))                                              AS SaleDate,
        ApplyMap('map_CustomerSurvivor', CustomerID, -1)                                 AS CustomerID,
        ApplyMap('map_ChannelName', Channel, 'Other')                                    AS ChannelName,
        Quantity * UnitPrice * (1 - DiscountPct / 100)                                   AS NetAmount
    RESIDENT [tmp_SalesRaw]
    WHERE Quantity > 0;

    INNER JOIN ([tmp_Sales])
    // -------------------------------------------------------------------------------------------------
    // Transforms Product.
    LOAD
        ProductID
    RESIDENT [Product];

    LEFT JOIN ([tmp_Sales])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_SaleRate.
    LOAD
        SaleDate,
        Currency,
        Rate
    RESIDENT [tmp_SaleRate]
    WHERE Currency <> '$(vBaseCurrency)';

DROP TABLE [tmp_SalesRaw];

DROP TABLE [tmp_ExchangeRate];
DROP TABLE [tmp_RateBridge];
DROP TABLE [tmp_SaleRate];

// -------------------------------------------------------------------------------------------------
// Raw load of tmp_AmountBand (no source set yet).
[tmp_AmountBand]:
    LOAD
        FromAmount,
        ToAmount,
        Band                                                                             AS AmountBand
    FROM [$(vPathRaw)AmountBands.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

// -------------------------------------------------------------------------------------------------
// Associates NetAmountCOP with the range [FromAmount, ToAmount] from tmp_AmountBand.
[tmp_BandBridge]:
    INTERVALMATCH (NetAmountCOP)
    LOAD
      FromAmount,
      ToAmount
    RESIDENT [tmp_AmountBand];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_BandBridge, combined via LEFT JOIN.
[tmp_SaleBand]:
    NOCONCATENATE
    LOAD
        NetAmountCOP,
        FromAmount,
        ToAmount
    RESIDENT [tmp_BandBridge];

    LEFT JOIN ([tmp_SaleBand])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_AmountBand.
    LOAD
        FromAmount,
        ToAmount,
        AmountBand
    RESIDENT [tmp_AmountBand];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_Sales, combined via LEFT JOIN.
[Sales]:
    NOCONCATENATE
    LOAD
        *,
        Round(NetAmount * Alt(Rate, 1))                                                  AS NetAmountCOP
    RESIDENT [tmp_Sales];

    LEFT JOIN ([Sales])
    // -------------------------------------------------------------------------------------------------
    // Transforms tmp_SaleBand.
    LOAD
        NetAmountCOP,
        AmountBand
    RESIDENT [tmp_SaleBand];

DROP TABLE [tmp_Sales];

DROP TABLE [tmp_AmountBand];
DROP TABLE [tmp_BandBridge];
DROP TABLE [tmp_SaleBand];

DROP FIELD SourceFile FROM [Sales];

STORE [Sales] INTO [$(vTxfPath)Sales.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms SaleReturn_PrecedingLoad, combined via INNER JOIN.
[SaleReturn]:
    LOAD
        ReturnID,
        SaleID,
        Reason,
        ReturnedQty,
        ReturnDate,
        If(Match(Reason, 'DAMAGED', 'EXPIRED'), 'Quality', 'Commercial')                 AS ReasonType
    ;
    // -------------------------------------------------------------------------------------------------
    // Raw load of SaleReturn_PrecedingLoad (no source set yet).
    LOAD
        ReturnID,
        SaleID,
        Reason,
        Quantity                                                                         AS ReturnedQty,
        Date(Date#(ReturnDate, 'YYYY-MM-DD'))                                            AS ReturnDate
    FROM [$(vPathRaw)Returns.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

    INNER JOIN ([SaleReturn])
    // -------------------------------------------------------------------------------------------------
    // Transforms Sales.
    LOAD
        SaleID,
        CustomerID,
        ProductID,
        SalespersonID,
        BranchID,
        Currency,
        ChannelName,
        NetAmountCOP / Quantity                                                          AS UnitNetCOP
    RESIDENT [Sales];

// -------------------------------------------------------------------------------------------------
// Transforms Sales, combined via CONCATENATE.
[Movement]:
    NOCONCATENATE
    LOAD
        SaleID,
        SaleDate                                                                         AS MovementDate,
        CustomerID,
        ProductID,
        SalespersonID,
        BranchID,
        Quantity,
        Currency,
        ChannelName,
        NetAmountCOP,
        AmountBand,
        'Sale'                                                                           AS MovementType
    RESIDENT [Sales];

    CONCATENATE ([Movement])
    // -------------------------------------------------------------------------------------------------
    // Transforms SaleReturn.
    LOAD
        SaleID,
        ReturnDate                                                                       AS MovementDate,
        CustomerID,
        ProductID,
        SalespersonID,
        BranchID,
        Currency,
        ChannelName,
        ReasonType,
        'Return'                                                                         AS MovementType,
        -ReturnedQty                                                                     AS Quantity,
        Round(-ReturnedQty * UnitNetCOP)                                                 AS NetAmountCOP
    RESIDENT [SaleReturn];

// -------------------------------------------------------------------------------------------------
// Raw load of Budget (no source set yet).
[Budget]:
    LOAD
        BranchID,
        Jan,
        Feb,
        Mar
    FROM [$(vPathRaw)Budget.csv] (txt, utf8, delimiter is ',', embedded labels, msq);

STORE [Movement] INTO [$(vTxfPath)Movement.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Unpivots columns into [BudgetMonthName] / [BudgetAmount] (CROSSTABLE).
[tmp_Budget]:
    CROSSTABLE('BudgetMonthName', 'BudgetAmount', 1)
    LOAD BranchID, Jan, Feb, Mar
    RESIDENT [Budget];

DROP TABLE [Budget];

// -------------------------------------------------------------------------------------------------
// Transforms tmp_Budget, combined via OUTER JOIN.
[BudgetVsActual]:
    LOAD
        BranchID,
        BudgetAmount,
        Match(BudgetMonthName, 'Jan', 'Feb', 'Mar')                                      AS MonthNum
    RESIDENT [tmp_Budget];

    OUTER JOIN ([BudgetVsActual])
    // -------------------------------------------------------------------------------------------------
    // Transforms Movement.
    LOAD
        BranchID,
        Num(Month(MovementDate))                                                         AS MonthNum,
        Sum(NetAmountCOP)                                                                AS ActualAmountCOP
    RESIDENT [Movement]
    GROUP BY BranchID, Num(Month(MovementDate));

DROP TABLE [tmp_Budget];

LET vMovementRows = NoOfRows('Movement');

// -------------------------------------------------------------------------------------------------
// IF $(vMovementRows) = 0, with ELSE.
IF $(vMovementRows) = 0 THEN

    TRACE ERROR: no sales reached the model, the reload stops here;

    EXIT SCRIPT;
ELSE

    TRACE Movements ready for the model: $(vMovementRows);
ENDIF

// -------------------------------------------------------------------------------------------------
// Transforms Movement.
[tmp_FactSales]:
    NOCONCATENATE
    LOAD
        SaleID,
        MovementDate                                                                     AS [%SK_Date],
        CustomerID                                                                       AS [%SK_Customer],
        ProductID                                                                        AS [%SK_Product],
        SalespersonID                                                                    AS [%SK_Salesperson],
        BranchID                                                                         AS [%SK_Branch],
        Quantity,
        Currency,
        ChannelName,
        NetAmountCOP,
        AmountBand,
        ReasonType,
        MovementType,
        AutoNumber(BranchID & '|' & Num(Month(MovementDate)))                            AS [%CK_BranchMonth]
    RESIDENT [Movement];

// -------------------------------------------------------------------------------------------------
// Transforms Movement.
[tmp_DateRange]:
    LOAD
        Min(MovementDate)                                                                AS MinDate,
        Max(MovementDate)                                                                AS MaxDate
    RESIDENT [Movement];

LET vMinDate = Num(Peek('MinDate', 0, 'tmp_DateRange'));
LET vMaxDate = Num(Peek('MaxDate', 0, 'tmp_DateRange'));

DROP TABLE [tmp_DateRange];

// -------------------------------------------------------------------------------------------------
// Builds a range with WHILE (field CalendarDate).
[tmp_CalendarBase]:
    LOAD
        Date($(vMinDate) + IterNo() - 1) AS [CalendarDate]
    AUTOGENERATE 1
    WHILE $(vMinDate) + IterNo() - 1 <= $(vMaxDate);

// -------------------------------------------------------------------------------------------------
// Transforms tmp_FactSales.
[Fact_Sales]:
    LOAD
        SaleID,
        [%SK_Customer],
        [%SK_Product],
        [%SK_Salesperson],
        [%SK_Branch],
        Quantity,
        Currency,
        ChannelName,
        NetAmountCOP,
        AmountBand,
        ReasonType,
        MovementType,
        [%CK_BranchMonth],
        [%SK_Date]
    RESIDENT [tmp_FactSales];

STORE [Fact_Sales] INTO [$(vPathModel)Fact_Sales.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms BudgetVsActual.
[Fact_BudgetVsActual]:
    LOAD
        BudgetAmount,
        ActualAmountCOP,
        AutoNumber(BranchID & '|' & MonthNum)                                            AS [%CK_BranchMonth]
    RESIDENT [BudgetVsActual];

RENAME FIELD BudgetAmount TO BudgetAmountCOP;

STORE [Fact_BudgetVsActual] INTO [$(vPathModel)Fact_BudgetVsActual.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms Customer.
[Dim_Customer]:
    LOAD
        CustomerID                                                                       AS [%SK_Customer],
        DocNumber,
        Segment,
        CreditLimit,
        CustomerName,
        DocTypeName,
        CustomerCity,
        SignupDate,
        CustomerStatus,
        CreditProfile
    RESIDENT [Customer];

STORE [Dim_Customer] INTO [$(vPathModel)Dim_Customer.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms Product.
[Dim_Product]:
    LOAD
        ProductID                                                                        AS [%SK_Product],
        SKU,
        ListPrice,
        UnitCost,
        UnitOfMeasure,
        CategoryName,
        Family,
        BrandName,
        CountryOfOrigin,
        ProductName,
        MarginPct
    RESIDENT [Product];

STORE [Dim_Product] INTO [$(vPathModel)Dim_Product.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms Branch.
[Dim_Branch]:
    LOAD
        BranchID                                                                         AS [%SK_Branch],
        BranchName,
        Region,
        BranchCity,
        BranchFormat
    RESIDENT [Branch];

STORE [Dim_Branch] INTO [$(vPathModel)Dim_Branch.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Transforms Salesperson.
[Dim_Salesperson]:
    LOAD
        SalespersonID                                                                    AS [%SK_Salesperson],
        HomeBranchID,
        MonthlyTarget,
        SalespersonName,
        HireDate
    RESIDENT [Salesperson];

STORE [Dim_Salesperson] INTO [$(vPathModel)Dim_Salesperson.qvd] (qvd);

// Checkpoint disabled from the toolbar; the lines below are commented out
// STORE [BudgetVsActual] INTO [$(vTxfPath)BudgetVsActual.qvd] (qvd);
// EXIT SCRIPT;

// -------------------------------------------------------------------------------------------------
// Transforms tmp_CalendarBase.
[Dim_Calendar]:
    LOAD
        CalendarDate,
        CalendarDate                                                                     AS [%SK_Date],
        Year(CalendarDate)                                                               AS Year,
        Num(Month(CalendarDate))                                                         AS MonthNum,
        Month(CalendarDate)                                                              AS Month,
        'Q' & Ceil(Month(CalendarDate) / 3)                                              AS Quarter,
        Week(CalendarDate)                                                               AS Week,
        WeekDay(CalendarDate)                                                            AS Weekday,
        If(WeekDay(CalendarDate) >= 5, 1, 0)                                             AS IsWeekend,
        If(Len(ApplyMap('map_Holiday', Num(CalendarDate), '')) > 0, 1, 0)                AS IsHoliday,
        ApplyMap('map_Holiday', Num(CalendarDate), Null())                               AS HolidayName
    RESIDENT [tmp_CalendarBase];

STORE [Dim_Calendar] INTO [$(vPathModel)Dim_Calendar.qvd] (qvd);

// -------------------------------------------------------------------------------------------------
// Kept as written in the imported script (no matching node type).
SignupCalendar:
    DECLARE FIELD DEFINITION Tagged ('$date')
    FIELDS
      Dual(Year($1), YearStart($1))                                                          AS [Year] Tagged ('$axis', '$year'),
      Month($1)                                                                              AS [Month] Tagged ('$month', '$cyclic'),
      Dual(Year($1) & '-Q' & Num(Ceil(Month($1)/3)), QuarterStart($1))                       AS [YearQuarter] Tagged ('$yearquarter', '$qualified');

// -------------------------------------------------------------------------------------------------
// Kept as written in the imported script (no matching node type).
DERIVE FIELDS FROM FIELDS [SignupDate] USING [SignupCalendar];

DROP TABLE [Branch];
DROP TABLE [BudgetVsActual];
DROP TABLE [Customer];
DROP TABLE [Movement];
DROP TABLE [Product];
DROP TABLE [SaleReturn];
DROP TABLE [Sales];
DROP TABLE [Salesperson];
DROP TABLE [tmp_CalendarBase];
DROP TABLE [tmp_FactSales];

TRACE Q-Expert Distributors reload finished;

Flow_6.qvs

Archivo · lo lee MUST_INCLUDE

No se pega: se guarda como archivo. En la pestaña Flow_6 de Generate Syntax, Export descarga Flow_6.qvs (Deploy escribe todas las pestañas en una carpeta de una vez). Ponlo en lib://QXDistributors/Transform/_QVS/Flow_6.qvs, la carpeta Transform/_QVS dentro de tu carpeta QXDistributors. Si el archivo no está, la recarga se detiene con un error: para eso existe MUST_INCLUDE.

Ver script · 30 líneas
/*----------------------------------------------------------------------------------------
	Description [Flow_6]:
		
	-------------------------------------------------------------------------------------
	Author:	The Q-Fábrica team - 
	Date:	[2026-11-02]
	-------------------------------------------------------------------------------------
	Changes
	-------------------------------------------------------------------------------------
	Author		Date		Detail
	-------------------------------------------------------------------------------------

	-------------------------------------------------------------------------------------
	Copyright © Q-Expert Distributors.
	Q-Expert Qlik Developer Framework. www.qfabrica.com
----------------------------------------------------------------------------------------*/

// -------------------------------------------------------------------------------------------------
// SWITCH '$(vEnvironment)': 2 branches plus DEFAULT.
SWITCH '$(vEnvironment)'
    CASE 'PROD'

        TRACE Production reload: the model QVDs are the ones the apps read;
    CASE 'QA'

        TRACE QA reload: check the model QVDs before promoting them;
    DEFAULT

        TRACE Development reload;
END SWITCH

05Por qué se hace así

06Lo que sigue

Episodio 7: ¿y si alguien te entrega este script ya escrito?

Antes de modelar

Q-Explorator es la herramienta de escritorio del equipo de Q-Fábrica para explorar archivos QVD, CSV, Excel y Parquet, y para convertir archivos CSV, TXT y Excel en QVD. Abre en ella los archivos de cada episodio para ver primero su contenido y su calidad.

Conoce Q-Explorator