7.1 🎯 Overview del módulo

El módulo más liviano y especializado. Es básicamente un consultor de precios por canal/sucursal. No sincroniza activamente: delega el sync al módulo /catalogo/admin/api_sync.php (fuera de /reportes/).

Archivos
6
~900 LOC totales
Listas configuradas
17
Definidas en LISTAS_DISPONIBLES
Columnas precio
22
lista_0 a lista_21 en BD
sync.php
76 B
Stub que redirige 301
Versión
sin v formal
Recomendado bumpear
Filas datos
~7.870
1 fila por artículo

7.2 📋 Las 17 listas configuradas

Definidas en config/database.php como LISTAS_DISPONIBLES:

id_listaNombreEmpresa
1ZA | DeliveryZA
3ZA | Lista N° 1ZA
4ZA | Lista N° 2ZA
5ZA | Lista Costo EspecialZA
7ZA | Lista VeterinariaZA
8ZA | Lista N° 3 C/VETEZA
18ZA | Lista Feed ChasisZA
19ZA | Lista Feed EquipoZA
21ZA | SALON MINORISTAZA
9TA | Precio ListaTA
10TA | Precio ContadoTA
11TA | Precio LunesTA
13TA | Desc. EspecialTA
14EB | Lista C/TarjetaEB
15EB | Lista ContadoEB
16EB | Lista MayoristaEB
17EB | Lista InternaEB
💡 Gaps en la numeración
Los IDs 2, 6, 12, 20 no están en uso por ZA — probablemente eran listas de otras empresas del grupo o quedaron como placeholders. En el dump real, la tabla articulos_precios tiene 22 columnas (lista_0 a lista_21), pero el schema definido por listas/api/_schema.php omite las no usadas (17 columnas + lista_0).

7.3 🗄️ Schema

3 tablas principales

                    ┌─────────────────────────┐
                    │  articulos_formas_agrupar│   maestro
                    │  PK id_forma_agrupar    │   (ej: MARCA, ACCESORI)
                    │  des_forma_agrupar      │
                    └────────────┬────────────┘
                                 │ 1
                                 │
                                 │ N
                    ┌────────────▼────────────┐
                    │  articulos_agrupaciones │   N:N artículo × forma
                    │  PK(id_articulo,        │
                    │     id_forma_agrupar)   │
                    │  des_agrupacion         │   (ej: COLLARES Y CORREA)
                    └─────────────────────────┘
                                  │ N
                                  │
                                  │ 1
                                  ▼
                    ┌─────────────────────────┐
                    │   articulos_precios     │   1 fila por artículo
                    │   PK id_articulo        │
                    │   lista_0 (precio base) │
                    │   lista_1, 3, 4, …, 21  │   precios por canal
                    │   visible_mobile        │
                    │   anulado               │
                    │   sync_fecha            │
                    └─────────────────────────┘
                                  │
                                  │ idx_des (búsqueda)
                                  ▼
                              (consultor)

listas_sync (metadata)

CREATE TABLE listas_sync (
  id_lista     TINYINT UNSIGNED PRIMARY KEY,
  nombre_lista VARCHAR(100),
  ultima_sync  DATETIME,
  total_filas  INT UNSIGNED DEFAULT 0
);

Migraciones idempotentes en _schema.php

// Backfill de visible_mobile desde articulos_raw
try {
    $pdo->exec(
        "UPDATE articulos_precios ap
         INNER JOIN articulos_raw ar ON ar.codigo = ap.id_articulo
         SET ap.visible_mobile = IF(ar.usado_disp_movil = 'NO', 0, 1)"
    );
} catch (Exception $e) {
    // articulos_raw puede no existir en todos los entornos
}

Esto ejecuta en cada hit a listas/index.php → garantiza que visible_mobile esté sincronizado con el catálogo. Pero ejecutar un UPDATE en cada hit es costoso si la tabla crece. Ver propuesta P-LIS-2.

7.4 🔍 index.php — el consultor

Flujo de uso

  1. Usuario selecciona lista (de las 17) y forma de agrupar (MARCA, CATEGORÍA, etc.).
  2. Click "Buscar" → llama api/precios.php?lista=N&forma=X.
  3. Muestra tabla agrupada por des_agrupacion con precio + cod_barra_bulto + bultos.

Endpoint api/precios.php

SELECT
  ap.id_articulo,
  ap.des_articulo,
  ap.cod_barra_bulto,
  ap.unidades_bulto,
  ap.lista_N           AS precio,
  ag.des_agrupacion
FROM articulos_precios ap
LEFT JOIN articulos_agrupaciones ag
       ON ag.id_articulo = ap.id_articulo
      AND ag.id_forma_agrupar = ?
WHERE ap.anulado = 0
  AND ap.visible_mobile = 1
  AND ap.lista_N IS NOT NULL
ORDER BY ag.des_agrupacion, ap.des_articulo;

UI

  • Tema dark/light con clave za-theme.
  • Tabla con tr.group-header para cada agrupación.
  • Filtro de búsqueda incremental (cliente-side).
  • Banner con listas_sync.ultima_sync ("Datos al 2026-05-20").

7.5 🔁 Sync delegado

// listas/sync.php
<?php
header('Location: /catalogo/admin/api_sync.php', true, 301);
exit;

Sólo 76 bytes. Redirige a /catalogo/admin/api_sync.php (fuera del módulo Reportes). Esto significa que el sync efectivo de precios lo hace otro módulo del sitio (/catalogo/), no el de Reportes.

⚠️ Acoplamiento implícito
Reportes/Listas depende de que /catalogo/admin/api_sync.php exista y funcione. Si /catalogo se reorganiza, este redirect rompe sin warning.

Existe también listas/api/sync_process.php (11.7 KB) que parecería ser una copia o backup del backend del sync — pero no se llama desde sync.php. Posible código muerto.

7.6 ⚠️ Hallazgos específicos del módulo

IDHallazgoPrioridad
LIS-01 listas/sync.php es un stub que redirige a otro módulo — acoplamiento implícito. Medio
LIS-02 listas/api/sync_process.php (11.7 KB) no se llama desde el stub — posible código muerto. Medio
LIS-03 Backfill de visible_mobile ejecuta UPDATE en CADA hit a index.php. Medio
LIS-04 22 columnas lista_N en BD vs 17 en el schema PHP → divergencia. Bajo
LIS-05 Sin versionado (version.php no existe). Medio
LIS-06 Agregar una lista nueva (id 22) requiere editar config/database.php + _schema.php + ALTER TABLE. Bajo
LIS-07 Sin auth — cualquiera puede consultar todos los precios (puede ser intencional para uso interno público). Bajo

7.7 🚀 Propuestas específicas del módulo

P-LIS-1 · Decidir si sync_process.php está vivo

Hacer grep -r "sync_process" /reportes/listas/ y si nadie lo llama, eliminarlo. Si está vivo (algún cron o llamada manual), documentarlo en el header del archivo.

P-LIS-2 · Mover el backfill a un cron / trigger

// En vez de UPDATE en cada hit:

-- Opción A: Cron nocturno
0 4 * * * php /reportes/listas/cron_backfill_visible.php

-- Opción B: Trigger SQL
CREATE TRIGGER trg_articulos_raw_to_precios
AFTER UPDATE ON articulos_raw FOR EACH ROW
BEGIN
  UPDATE articulos_precios
     SET visible_mobile = IF(NEW.usado_disp_movil = 'NO', 0, 1)
   WHERE id_articulo = NEW.codigo;
END;

P-LIS-3 · Normalizar schema a tabla larga

22 columnas lista_N es denormalización extrema. Refactor:

CREATE TABLE articulos_precios_v2 (
  id_articulo  INT NOT NULL,
  id_lista     SMALLINT UNSIGNED NOT NULL,
  precio       DECIMAL(14,4),
  sync_fecha   DATETIME,
  PRIMARY KEY (id_articulo, id_lista),
  INDEX idx_lista (id_lista)
);

-- Migración: PIVOT desde la tabla actual
INSERT INTO articulos_precios_v2 (id_articulo, id_lista, precio, sync_fecha)
SELECT id_articulo, 0,  lista_0,  sync_fecha FROM articulos_precios WHERE lista_0  IS NOT NULL
UNION ALL
SELECT id_articulo, 1,  lista_1,  sync_fecha FROM articulos_precios WHERE lista_1  IS NOT NULL
UNION ALL
SELECT id_articulo, 3,  lista_3,  sync_fecha FROM articulos_precios WHERE lista_3  IS NOT NULL
-- ... etc
;

Ventajas: agregar una lista nueva no requiere ALTER TABLE; queries más flexibles; historial por lista trivial.

P-LIS-4 · UI editor de precios

Hoy es solo lectura. Sería útil:

  • Permitir editar precios (cuando exista auth) sin re-subir Excel completo.
  • Bulk update por % (ej. "subir lista 7 un 10%").
  • Preview del impacto antes de guardar.
  • Audit log de cambios manuales.

P-LIS-5 · Catálogo de listas en DB

Mover LISTAS_DISPONIBLES de config/database.php a DB:

CREATE TABLE listas_catalogo (
  id_lista      SMALLINT UNSIGNED PRIMARY KEY,
  empresa       VARCHAR(10),
  nombre_corto  VARCHAR(50),
  nombre_largo  VARCHAR(100),
  visible       TINYINT(1) DEFAULT 1,
  orden         TINYINT DEFAULT 0
);

UI admin para editar sin tocar PHP.

P-LIS-6 · Export PDF de la lista

Botón "Exportar PDF" en el consultor → genera PDF con header de la lista, fecha, agrupación, total de productos. Útil para enviar a clientes finales por WhatsApp.