{ ------------------------------------------------------------------------------- PROGRAMA : IPPRRP014 (OPTIMIZADO) OBJETIVO : REPORTE DIAS DE INVENTARIOS - GERENTE DE MARCAS PROGRAMADOR : Ing. Juan Francisco Soto FECHA REALIZACION : Agosto 19, 1992 FECHA ULT. MODIFICA : Julio 07, 2026 DESCRIPCION : OPTIMIZACION DE RENDIMIENTO: 1. Ultima venta, ultima compra y cantidad comprada se calculan en UN SOLO query (LEFT JOIN a subconsultas agregadas) en lugar de 3 SELECTs por producto dentro del REPORT. Se elimino la tautologia d.numero_orden = d.numero_orden. 2. Se elimino DISPLAY por fila y el refresh de progreso se hace cada 50 filas (cada refresh es un round-trip al GDC). 3. Se elimino el SELECT a companias dentro de BEFORE GROUP OF (ya esta cargado en MAIN). 4. CORRECCION FORMULA ROTACION: Antes: rotacion = existencia / consumo (cobertura) Ahora: rotacion = consumo / existencia (indice de rotacion estandar: veces que el inventario rota en el periodo). Se agrega ademas la rotacion anualizada = rotacion * (365/dias). DIAS INV se mantiene igual (formula correcta). AGOSTO 20, 2026 - MARGEN + DETALLADO/RESUMEN: 5. MARGEN DE BENEFICIO: se calcula el precio de venta REAL promedio del periodo (ponderado por cantidad, neto del descuento de cabecera) desde vetb00002/vetb00003, y el COSTO estandar (iptb00004, antes quedaba en 0). Margen % sobre precio = (precio-costo)/precio*100. Clasificacion por cuadrante margen x rotacion (ESTRELLA/VOLUMEN/NICHO/ REVISAR/SIN COSTO), umbrales g_margen_min / g_rot_min. 6. El formulario permite elegir DETALLADO (una linea por producto -> ipprrp014.4rp) o RESUMEN por CLASE cod_n (subtotales -> ipprrp014r.4rp). La agregacion por clase se hace en el FOREACH (emite_clase) y el reporte 'resumen' es PLANO (una fila por clase, total al pie). NOTA: el consumo del 014 es de PRODUCCION (pcantidadConduce); el margen ponderado de la clase pesa por ese consumo. ------------------------------------------------------------------------------- } GLOBALS "ipprgb000.4gl" DEFINE sale CHAR(1) DEFINE p_ano CHAR(4) DEFINE fecha2 DATE DEFINE dias1 SMALLINT, m_meses SMALLINT, conmes, valor1 DECIMAL(12, 2), representa VARCHAR(100), num_representa INT, chexistencia CHAR(2), progreso, cuenta_reg, cuenta INT # Umbrales de clasificacion margen/rotacion (ajustables aqui) DEFINE g_margen_min DECIMAL(6,2) DEFINE g_rot_min DECIMAL(6,2) # Acumulador por CLASE para el reporte Resumen (una fila por cod_n), # a nivel de modulo para que emite_clase() y la funcion principal lo compartan. DEFINE rc RECORD cod_n INTEGER, nombre_clase CHAR(30), categoria CHAR(30), c_prod INTEGER, c_estrella INTEGER, c_revisar INTEGER, c_valor_inv DECIMAL(16,2), c_ventas DECIMAL(16,2), c_ganancia DECIMAL(16,2), c_margen DECIMAL(12,2), c_rot_prom DECIMAL(12,4) END RECORD DEFINE rc_rot_sum DECIMAL(16,4) DEFINE prev_cod_n INTEGER DEFINE cbtipo ui.ComboBox MAIN DEFER INTERRUPT CALL ARG_VAL(1) RETURNING usuarios CALL ARG_VAL(2) RETURNING clave CONNECT TO "smarmotech" USER usuarios USING clave SELECT a.* INTO p_companias.* FROM companias a CALL ipprrp013() END MAIN FUNCTION ipprrp013() # Variable que toma el valor de los dias que se va a valorar el reporte DEFINE c_dias INTEGER # Variables varias para el control de busqueda de las informaciones DEFINE selec1, select_bal STRING # Datos precalculados por SQL (antes se buscaban con 3 SELECTs por fila) DEFINE w_fecha_v DATE, w_fecha_oc DATE, w_tcantidad DEC(12, 4) DEFINE rotacion_anual DEC(12, 2) # Record para la impresion del reporte DEFINE estadis RECORD cod_n LIKE intb00001.cod_n, cod_grupo LIKE intb00001.cod_grupo, cod_tipo LIKE intb00001.cod_tipo, cod_sec LIKE intb00001.cod_sec, descrip_esp LIKE intb00001.descrip_esp, unidad_med LIKE iptb00002.unidad_med, consumo DEC(12, 4), grupo CHAR(30), tipo CHAR(30), codCategoria INT, categoria CHAR(30), fecha_v, fecha_oc DATE, tcantidad DEC(12, 4), existe DEC(12, 4), valor DECIMAL(12, 4), costo DECIMAL(12, 4), dias INTEGER, pcodigo CHAR(25), cantidad_orden, pcantidadConduce, transito, existencia, reservado, disponible, precio, rotacion DEC(12, 2), # --- Analisis margen (Agosto 2026) --- rotacion_anual DEC(12,4), margen DEC(12,2), valor_inv DEC(14,2), ventas_valor DEC(14,2), ganancia DEC(14,2), clasificacion CHAR(12) END RECORD DEFINE handler om.SaxDocumentHandler, r_filename STRING, r_output STRING, preview INTEGER OPTIONS FORM LINE 8, ERROR LINE 23, COMMENT LINE 21 OPEN FORM ipfmrp014 FROM "ipfmrp014" DISPLAY FORM ipfmrp014 CALL ui.Window.getCurrent().setText("Dias de Inventarios Gerente Marcas") DISPLAY "ipprrp014" AT 4, 3 ATTRIBUTE(RED) DISPLAY "Dias de Inventarios Gerente Marcas" AT 6, 33 ATTRIBUTE(BLACK) INPUT BY NAME fecha1, fecha2, c_dias, m_meses, chexistencia, tipo_reporte BEFORE INPUT LET progreso = 10 LET c_dias = 0 # Umbrales de clasificacion margen/rotacion LET g_margen_min = 25.00 LET g_rot_min = 2.00 # Combo Detallado / Resumen (llenado por codigo) LET cbtipo = ui.ComboBox.forName("formonly.tipo_reporte") IF cbtipo IS NOT NULL THEN CALL cbtipo.clear() CALL cbtipo.addItem("D","Detallado (por producto)") CALL cbtipo.addItem("R","Resumen (por clase)") END IF IF tipo_reporte IS NULL OR tipo_reporte = " " THEN LET tipo_reporte = "D" END IF DISPLAY BY NAME c_dias, progreso, tipo_reporte SELECT DISTINCT RTRIM(a.nom1_emp) + ' ' + RTRIM(a.apell1_emp), a.num_emp INTO representa, num_representa FROM adtb00003 a, seg0001 b WHERE a.num_emp = b.num_emp AND b.usuario = usuarios AND a.gerentemarcas = 'SI' IF STATUS = NOTFOUND THEN CALL fgl_winmessage( "INFO", "USTED NO ES GERENTE DE MARCAS, NO PUEDE GENERAR ESTE INFORME", "INFO") EXIT PROGRAM END IF DISPLAY BY NAME representa AFTER FIELD c_dias IF c_dias IS NULL THEN LET numero_msg = 16 CALL msg(numero_msg) NEXT FIELD c_dias END IF AFTER FIELD fecha1 IF fecha1 IS NULL THEN LET numero_msg = 16 CALL msg(numero_msg) NEXT FIELD fecha1 END IF AFTER FIELD fecha2 IF fecha2 IS NULL THEN LET numero_msg = 16 CALL msg(numero_msg) NEXT FIELD fecha2 END IF AFTER FIELD m_meses IF m_meses IS NULL OR m_meses = 0 THEN LET numero_msg = 16 CALL msg(numero_msg) NEXT FIELD m_meses END IF AFTER FIELD tipo_reporte IF tipo_reporte IS NULL OR tipo_reporte = " " THEN LET tipo_reporte = "D" DISPLAY BY NAME tipo_reporte END IF AFTER INPUT IF int_flag THEN LET numero_msg = 2 CALL msg(numero_msg) LET int_flag = FALSE RETURN END IF IF chexistencia IS NULL THEN NEXT FIELD chexistencia END IF END INPUT LET dias1 = fecha2 - fecha1 LET dias1 = dias1 + 1 DISPLAY BY NAME dias1 SELECT MAX(a.ano) INTO p_ano FROM prdtable a WHERE a.fecha_corte <= fecha2 CONSTRUCT criterio ON x.cod_categoria, a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec FROM cod_cat, cod_n, cod_grupo, cod_tipo, cod_sec BEFORE CONSTRUCT CALL categorias() AFTER CONSTRUCT IF int_flag THEN LET numero_msg = 2 CALL msg(numero_msg) LET int_flag = FALSE RETURN END IF EXIT CONSTRUCT END CONSTRUCT # Conteo para la barra de progreso (sin ORDER BY, innecesario en un COUNT) LET select_bal = "SELECT COUNT(*) FROM iptb00002 a INNER JOIN iptb00035 x1 ON a.cod_n = x1.cod_n INNER JOIN iptb00029 d ON a.cod_grupo = d.cod_grupo INNER JOIN iptb00034 x ON x.cod_categoria = x1.cod_cat where ", criterio CLIPPED, " and a.num_emp = ", num_representa PREPARE comando1 FROM select_bal EXECUTE comando1 INTO cuenta_reg # ============================================================================== # QUERY PRINCIPAL OPTIMIZADO (CONJUNTO, NO FILA POR FILA) # ------------------------------------------------------------------------------ # Las subconsultas uv (ultima venta) y uc (ultima compra + cantidad) se agregan # UNA sola vez por GROUP BY y se unen por LEFT JOIN. SQL Server recorre las # tablas de historico una vez en total, en lugar de una vez por cada producto. # Se corrigio la condicion muerta "d.numero_orden = d.numero_orden". # ============================================================================== LET select_bal = "SELECT a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec, a.descrip_esp,", " a.unidad_med, '0', d.descripcion, ' ', x.cod_categoria, x.descripcion,", " uv.fecha_v,", " uc.fecha_oc,", " ISNULL(uc.tcantidad, 0)", " FROM iptb00002 a", " INNER JOIN iptb00035 x1 ON a.cod_n = x1.cod_n", " INNER JOIN iptb00029 d ON a.cod_grupo = d.cod_grupo", " INNER JOIN iptb00034 x ON x.cod_categoria = x1.cod_cat", " LEFT JOIN (", " SELECT dv.cod_n, dv.cod_grupo, dv.cod_tipo, dv.cod_sec,", " MAX(fv.fecha_factura) AS fecha_v", " FROM vetb00003 dv", " INNER JOIN vetb00002 fv ON dv.factura = fv.factura", " WHERE fv.status_t IS NULL", " GROUP BY dv.cod_n, dv.cod_grupo, dv.cod_tipo, dv.cod_sec", " ) uv ON uv.cod_n = a.cod_n AND uv.cod_grupo = a.cod_grupo", " AND uv.cod_tipo = a.cod_tipo AND uv.cod_sec = a.cod_sec", " LEFT JOIN (", " SELECT dc.cod_n, dc.cod_grupo, dc.cod_tipo, dc.cod_sec,", " MAX(oc.fech_oc) AS fecha_oc,", " SUM(dc.cantidad) AS tcantidad", " FROM cotb00015 dc", " INNER JOIN cotb00014 oc ON dc.num_oc = oc.num_oc", " WHERE dc.status_t IS NULL", " GROUP BY dc.cod_n, dc.cod_grupo, dc.cod_tipo, dc.cod_sec", " ) uc ON uc.cod_n = a.cod_n AND uc.cod_grupo = a.cod_grupo", " AND uc.cod_tipo = a.cod_tipo AND uc.cod_sec = a.cod_sec", " WHERE ", criterio CLIPPED, " AND a.num_emp = ", num_representa, " ORDER BY x.cod_categoria, a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec" PREPARE comando2 FROM select_bal DECLARE accion CURSOR FOR comando2 # Detallado (D) -> ipprrp014.4rp ; Resumen por clase (R) -> ipprrp014r.4rp IF tipo_reporte = "R" THEN LET r_filename = "ipprrp014r.4rp" ELSE LET r_filename = "ipprrp014.4rp" END IF LET idx = 1 LET prev_cod_n = -999999 DISPLAY progreso TO progreso CALL ui.Interface.refresh() FOREACH accion INTO estadis.* LET estadis.pcodigo = estadis.cod_n USING "&&&&", "-", estadis.cod_grupo USING "&&&&", "-", estadis.cod_tipo USING "&&&&", "-", estadis.cod_sec USING "&&&&&&" IF int_flag THEN LET numero_msg = 2 CALL msg(numero_msg) LET int_flag = FALSE LET estadis.consumo = 0 LET estadis.existe = 0 LET sale = "N" RETURN END IF IF idx = 1 THEN IF fgl_report_loadCurrentSettings(r_filename) THEN CALL seleccionarSalida() RETURNING r_output LET preview = 1 CALL fgl_report_selectDevice(r_output) CALL fgl_report_selectPreview(preview) CALL fgl_report_configurexlsxdevice(null,null,null,false,false,null,1) LET handler = fgl_report_commitCurrentSettings() ELSE EXIT PROGRAM END IF IF handler IS NOT NULL THEN IF tipo_reporte = "R" THEN START REPORT resumen TO XML HANDLER handler ELSE START REPORT estadistica TO XML HANDLER handler END IF ELSE EXIT FOREACH END IF END IF # Busqueda de existencias / consumo (pendiente de convertir a conjunto, # ver nota al final del programa) CALL disponibles( estadis.cod_n, estadis.cod_grupo, estadis.cod_tipo, estadis.cod_sec, fecha2) RETURNING estadis.cantidad_orden, estadis.pcantidadConduce, estadis.transito, estadis.existencia, estadis.reservado, valororden IF chexistencia = 'NO' AND estadis.existencia <= 0 THEN LET idx = idx + 1 CONTINUE FOREACH END IF LET estadis.consumo = estadis.pcantidadConduce IF estadis.consumo < 0 THEN LET estadis.consumo = estadis.consumo * -1 END IF LET estadis.existe = estadis.existencia # ---- COSTO ESTANDAR (iptb00004). Antes quedaba en 0; ahora se calcula # para poder obtener el margen y el valor de exceso reales. ---- LET estadis.costo = 0 SELECT AVG(a.material+a.labor+a.gastos) INTO estadis.costo FROM iptb00004 a WHERE a.ano = YEAR(TODAY) AND a.mes_fin = 12 AND a.cod_n = estadis.cod_n AND a.cod_grupo = estadis.cod_grupo AND a.cod_tipo = estadis.cod_tipo AND a.cod_sec = estadis.cod_sec AND a.status_t IS NULL IF estadis.costo IS NULL THEN LET estadis.costo = 0 END IF # ---- PRECIO de venta REAL promedio del periodo (ponderado por cantidad, # neto del descuento de cabecera). Fuente vetb00002 + vetb00003. ---- LET estadis.precio = 0 SELECT SUM(d.cantidad * d.precio * (1 - (ISNULL(h.porc_desc,0)/100.0))) / NULLIF(SUM(d.cantidad),0) INTO estadis.precio FROM vetb00003 d, vetb00002 h WHERE d.factura = h.factura AND d.cod_n = estadis.cod_n AND d.cod_grupo = estadis.cod_grupo AND d.cod_tipo = estadis.cod_tipo AND d.cod_sec = estadis.cod_sec AND h.status_t IS NULL AND d.status_t IS NULL AND h.fecha_factura BETWEEN fecha1 AND fecha2 IF estadis.precio IS NULL THEN LET estadis.precio = 0 END IF # Calculo para los dias de produccion IF estadis.existe > 0 THEN LET estadis.cantidad_orden = estadis.cantidad_orden - estadis.pcantidadConduce LET estadis.disponible = estadis.existe - estadis.cantidad_orden - estadis.reservado IF estadis.consumo > 0 THEN LET estadis.dias = (estadis.existe / estadis.consumo) * dias1 # indice de rotacion = consumo / existencia (veces en el periodo) LET estadis.rotacion = estadis.consumo / estadis.existe # anualizada: 365.0 (decimal) evita la division entera LET estadis.rotacion_anual = estadis.rotacion * (365.0 / dias1) ELSE LET estadis.rotacion = 0 LET estadis.rotacion_anual = 0 END IF ELSE LET estadis.dias = 0 LET estadis.rotacion = 0 LET estadis.rotacion_anual = 0 END IF LET rotacion_anual = estadis.rotacion_anual LET estadis.valor = 0 IF estadis.dias >= c_dias THEN IF estadis.dias > 0 THEN LET valor1 = (estadis.existe * c_dias) / estadis.dias LET estadis.valor = (estadis.existe - valor1) * estadis.costo ELSE LET estadis.valor = 0 END IF END IF # ---- Margen % sobre precio, valores y clasificacion por cuadrante ---- IF estadis.precio > 0 AND estadis.costo > 0 THEN LET estadis.margen = ((estadis.precio - estadis.costo) / estadis.precio) * 100 ELSE LET estadis.margen = 0 END IF LET estadis.valor_inv = estadis.existe * estadis.costo LET estadis.ventas_valor = estadis.consumo * estadis.precio LET estadis.ganancia = estadis.consumo * (estadis.precio - estadis.costo) CASE WHEN estadis.costo <= 0 LET estadis.clasificacion = "SIN COSTO" WHEN estadis.margen >= g_margen_min AND estadis.rotacion_anual >= g_rot_min LET estadis.clasificacion = "ESTRELLA" WHEN estadis.margen < g_margen_min AND estadis.rotacion_anual >= g_rot_min LET estadis.clasificacion = "VOLUMEN" WHEN estadis.margen >= g_margen_min AND estadis.rotacion_anual < g_rot_min LET estadis.clasificacion = "NICHO" OTHERWISE LET estadis.clasificacion = "REVISAR" END CASE IF tipo_reporte = "R" THEN # Agregacion por CLASE: al cambiar cod_n, emite la clase anterior IF prev_cod_n <> estadis.cod_n THEN IF prev_cod_n <> -999999 THEN CALL emite_clase() END IF LET prev_cod_n = estadis.cod_n LET rc.cod_n = estadis.cod_n LET rc.categoria = estadis.categoria LET rc.nombre_clase = NULL SELECT a.producto INTO rc.nombre_clase FROM iptb00020 a WHERE a.cod_n = estadis.cod_n IF rc.nombre_clase IS NULL THEN LET rc.nombre_clase = " " END IF LET rc.c_prod = 0 LET rc.c_estrella = 0 LET rc.c_revisar = 0 LET rc.c_valor_inv = 0 LET rc.c_ventas = 0 LET rc.c_ganancia = 0 LET rc_rot_sum = 0 END IF LET rc.c_prod = rc.c_prod + 1 LET rc.c_valor_inv = rc.c_valor_inv + estadis.valor_inv LET rc.c_ventas = rc.c_ventas + estadis.ventas_valor LET rc.c_ganancia = rc.c_ganancia + estadis.ganancia LET rc_rot_sum = rc_rot_sum + estadis.rotacion_anual IF estadis.clasificacion = "REVISAR" THEN LET rc.c_revisar = rc.c_revisar + 1 END IF IF estadis.clasificacion = "ESTRELLA" THEN LET rc.c_estrella = rc.c_estrella + 1 END IF ELSE OUTPUT TO REPORT estadistica(estadis.*, dias1, c_dias, estadis.rotacion_anual) END IF LET idx = idx + 1 LET estadis.consumo = 0 LET estadis.existe = 0 LET estadis.dias = 0 LET estadis.rotacion = 0 LET estadis.rotacion_anual = 0 LET estadis.precio = 0 LET estadis.margen = 0 # Refrescar progreso solo cada 50 filas: cada refresh es un # round-trip al GDC y hacerlo por fila cuesta minutos. IF idx MOD 50 = 0 THEN LET progreso = (idx / cuenta_reg) * 100 DISPLAY progreso TO progreso CALL ui.Interface.refresh() END IF END FOREACH IF idx = 1 THEN CALL fgl_winmessage( "INFO", "NO EXISTEN INFORMACIONES CON ESTOS PARAMETROS", "INFO") ELSE # Emite la ultima clase acumulada (modo Resumen) IF tipo_reporte = "R" AND prev_cod_n <> -999999 THEN CALL emite_clase() END IF LET progreso = 100 DISPLAY progreso TO progreso IF tipo_reporte = "R" THEN FINISH REPORT resumen ELSE FINISH REPORT estadistica END IF END IF END FUNCTION # ------------------------------------------------------------------------------ # Calcula margen ponderado y rotacion promedio de la clase acumulada en 'rc' y la # envia al reporte 'resumen'. Usada al cambiar de CLASE y al terminar el FOREACH. # ------------------------------------------------------------------------------ FUNCTION emite_clase() IF rc.c_ventas > 0 THEN LET rc.c_margen = (rc.c_ganancia / rc.c_ventas) * 100 ELSE LET rc.c_margen = 0 END IF IF rc.c_prod > 0 THEN LET rc.c_rot_prom = rc_rot_sum / rc.c_prod ELSE LET rc.c_rot_prom = 0 END IF OUTPUT TO REPORT resumen(rc.*) END FUNCTION # ============================================================================== # REPORTE RESUMEN POR CLASE (cod_n) -- version PLANA (una fila por clase). # La agregacion la hace el FOREACH (emite_clase); aqui solo se imprime y se # acumula el gran total para el pie. Alimenta ipprrp014r.4rp. # ============================================================================== REPORT resumen(r) DEFINE r RECORD cod_n INTEGER, nombre_clase CHAR(30), categoria CHAR(30), c_prod INTEGER, c_estrella INTEGER, c_revisar INTEGER, c_valor_inv DECIMAL(16,2), c_ventas DECIMAL(16,2), c_ganancia DECIMAL(16,2), c_margen DECIMAL(12,2), c_rot_prom DECIMAL(12,4) END RECORD DEFINE hora CHAR(5), fecha DATE, g_prod INTEGER, g_valor_inv, g_ventas, g_ganancia DECIMAL(16,2), g_margen DECIMAL(12,2) FORMAT FIRST PAGE HEADER LET hora = TIME LET fecha = TODAY LET lj = (120 - LENGTH(p_companias.nombre CLIPPED)) / 2 LET g_prod = 0 LET g_valor_inv = 0 LET g_ventas = 0 LET g_ganancia = 0 ON EVERY ROW LET g_prod = g_prod + r.c_prod LET g_valor_inv = g_valor_inv + r.c_valor_inv LET g_ventas = g_ventas + r.c_ventas LET g_ganancia = g_ganancia + r.c_ganancia IF g_ventas > 0 THEN LET g_margen = (g_ganancia / g_ventas) * 100 ELSE LET g_margen = 0 END IF PRINTX r.*, g_prod, g_valor_inv, g_ventas, g_ganancia, g_margen, p_companias.*, fecha, hora, fecha1, fecha2 END REPORT REPORT estadistica(x, dias2, c_dias, rot_anual) DEFINE x RECORD cod_n LIKE intb00001.cod_n, cod_grupo LIKE intb00001.cod_grupo, cod_tipo LIKE intb00001.cod_tipo, cod_sec LIKE intb00001.cod_sec, descrip_esp LIKE intb00001.descrip_esp, unidad_med LIKE iptb00002.unidad_med, consumo LIKE iptb00006.cantidad_1, grupo CHAR(30), tipo CHAR(30), codCategoria INT, categoria CHAR(30), fecha_v, fecha_oc DATE, tcantidad DEC(12, 4), existe DEC(12, 4), valor DECIMAL(12, 3), costo DECIMAL(12, 4), dias INTEGER, pcodigo CHAR(25), cantidad_orden, pcantidadConduce, transito, existencia, reservado, disponible, precio, rotacion DEC(12, 2), # --- Analisis margen (Agosto 2026) - debe reflejar estadis --- rotacion_anual DEC(12,4), margen DEC(12,2), valor_inv DEC(14,2), ventas_valor DEC(14,2), ganancia DEC(14,2), clasificacion CHAR(12) END RECORD, tcantidad DEC(12, 4), rot_anual DEC(12, 2) DEFINE c_dias SMALLINT, dias_oc INTEGER DEFINE m_total DECIMAL(10, 2) DEFINE dias2 SMALLINT DEFINE hora CHAR(5) DEFINE t_total, t_valor DECIMAL(12, 2), chorden STRING, porden INTEGER, fecha DATE, pcosto DEC(12, 4), total_registros SMALLINT, total_grupo SMALLINT, nombre CHAR(50), cantidad_or DEC(12, 4) FORMAT FIRST PAGE HEADER LET hora = TIME LET fecha = TODAY LET total_registros = 0 LET lj = (120 - LENGTH(p_companias.nombre CLIPPED)) / 2 PRINTX dias1 BEFORE GROUP OF x.cod_grupo LET total_grupo = 0 LET hora = TIME # OPTIMIZACION: se elimino el SELECT a companias por cada grupo; # p_companias ya se carga una sola vez en MAIN. ON EVERY ROW LET total_registros = total_registros + 1 LET total_grupo = total_grupo + 1 LET nombre_ant = nombre # OPTIMIZACION: fecha_v, fecha_oc y tcantidad ya vienen # calculadas desde el query principal (LEFT JOIN agregado). # Antes aqui se ejecutaban 3 SELECTs por cada producto. LET conmes = 0 IF t_total IS NULL THEN LET t_total = 0 END IF IF t_valor IS NULL THEN LET t_valor = 0 END IF LET m_total = x.existe * x.costo LET cantidad_or = tcantidad + x.existe IF x.consumo > 0 THEN LET dias_oc = (cantidad_or / x.consumo) * dias1 END IF IF m_meses > 0 THEN LET conmes = x.consumo / m_meses END IF PRINTX x.cod_n, x.cod_grupo, x.cod_tipo, x.cod_sec, x.descrip_esp, x.consumo, x.existe, x.dias, x.rotacion, rot_anual, x.margen, x.clasificacion, x.unidad_med, fecha, hora, fecha1, fecha2, x.fecha_oc, chorden, x.tcantidad, p_companias.*, pcosto, total_registros, total_grupo, x.fecha_v END REPORT { ================================================================================ NOTA DE OPTIMIZACION PENDIENTE - disponibles() -------------------------------------------------------------------------------- El unico trabajo por-fila que queda es CALL disponibles() (en ipprgb000.4gl), que probablemente ejecuta varios SELECT SUM sobre las tablas de movimientos por cada producto. Para llevar el reporte de minutos a segundos, ese calculo tambien debe convertirse a conjunto: un solo query con GROUP BY por cod_n/cod_grupo/cod_tipo/cod_sec (o una tabla temporal #existencias) unido por LEFT JOIN al query principal, igual que se hizo con ultima venta/compra. ================================================================================ }