4.1 ๐ŸŽฏ Overview del mรณdulo

Mรณdulo mรกs complejo desde el punto de vista de ingestiรณn. Procesa 6 archivos Excel del ERP por mes, aplica un pipeline de clasificaciรณn 3-tier y alimenta 3 dashboards independientes (Gastos, Tesorerรญa, Finanzas) que comparten la misma base de datos.

Versiรณn
v2.0.2
Schema v2 ยท todas las raw histรณricas
Tablas
10
1 control + 1 derivada + 6 raw + 2 meta
Submรณdulos
3
gastos ยท tesorerรญa ยท finanzas
Filas raw totales
~330 K
egresos_detalle es la grande (262K)
Perรญodos cargados
22
(de caja_sincronizaciones.AI = 23)
CSS del mรณdulo
66 KB
Compartido por todos los submรณdulos

4.2 ๐Ÿงฉ Los 3 submรณdulos del hub

๐Ÿ’ธ

/gastos/

Pipeline de clasificaciรณn 3-tier que convierte 6 archivos Excel en gastos clasificados por rubro ร— concepto ร— provincia.

Tabla: caja_gastos Tabulator รกrbol
๐Ÿฆ

/tesoreria/

Flujo de caja, formas de cobro/pago, origen de cobros, cheques en cartera y alertas (5 pestaรฑas).

5 APIs: flujo ยท cobros ยท origen ยท cajas ยท alertas 53 KB index.php
๐Ÿ“Š

/finanzas/

IVA, percepciones, centros de costo, top proveedores y cruce de rentabilidad con ventas (3 pestaรฑas).

5 APIs: iva ยท perc ยท cc ยท prov ยท cruce 37 KB index.php

Mapa funcional

                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                          โ”‚   /reportes/caja/          โ”‚   Hub (landing)
                          โ”‚   index.php                โ”‚   cards + KPIs globales
                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                       โ”‚
                                       โ”‚ via header sticky con sub-tabs
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚                          โ”‚                          โ”‚
            โ–ผ                          โ–ผ                          โ–ผ
       /gastos/                  /tesoreria/                 /finanzas/
       index.php                 index.php                   index.php
       (Tabulator รกrbol)         (5 pestaรฑas)                (3 pestaรฑas)
            โ”‚                          โ”‚                          โ”‚
            โ”‚ api/data.php             โ”‚ api/flujo ยท cobros       โ”‚ api/iva ยท perc ยท cc
            โ”‚                          โ”‚     ยท origen ยท cajas     โ”‚     ยท proveedores
            โ”‚                          โ”‚     ยท alertas            โ”‚     ยท cruce_ventas
            โ”‚                          โ”‚                          โ”‚
            โ–ผ                          โ–ผ                          โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚              BASE DE DATOS COMPARTIDA (Schema v2)                โ”‚
   โ”‚   caja_sincronizaciones ยท caja_gastos ยท caja_raw_*  (10 tablas)  โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ–ฒ                                                  โ–ฒ
            โ”‚                                                  โ”‚
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ sync compartido (alimenta a TODOS) โ”€โ”€โ”€โ”€โ”˜
                       /caja/sync.php (individual)
                       /caja/sync_bulk.php (masivo multi-mes)

4.3 โ›“๏ธ Pipeline de clasificaciรณn 3-tier

Para cada fila de EgresosDetalle filtrada por operaciรณn de gasto (compras / gastos - caja o gastos - liquidaciones):

   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚  EgresosDetalle (gasto detectado)                               โ”‚
   โ”‚  Extrae nยบ ME de col "Observaciones" via regex:                 โ”‚
   โ”‚    /ME\s+0*(\d+)-0*(\d+)/i  โ†’  (serie, numero, empresa)         โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                   โ”‚
                                   โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚  NIVEL 1: GastosTipificados (PRIMARIA)                          โ”‚
   โ”‚  ยฟExiste (serie, numero, empresa) en gastosMap?                 โ”‚
   โ”‚  โ”œโ”€โ”€ Sร โ†’ emite 1 fila por cada lรญnea GT del comprobante        โ”‚
   โ”‚  โ”‚       rubro_id   = GT.rubro_id                               โ”‚
   โ”‚  โ”‚       rubro      = GT.rubro                                  โ”‚
   โ”‚  โ”‚       concepto_libre = GT.concepto                           โ”‚
   โ”‚  โ””โ”€โ”€ NO โ†’ continรบa al nivel 2 โ†“                                 โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                   โ”‚
                                   โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚  NIVEL 2: MovimientoDetalle + ConceptosDeGastos (FALLBACK)      โ”‚
   โ”‚  ยฟExiste (serie, numero, empresa) en movDetMap?                 โ”‚
   โ”‚  โ”œโ”€โ”€ Sร โ†’ tomar mdEntry.concepto_id                             โ”‚
   โ”‚  โ”‚        โ”œโ”€โ”€ existe en conceptosMap โ†’ usar c.rubro_id, c.rubro โ”‚
   โ”‚  โ”‚        โ””โ”€โ”€ no existe โ†’ rubro = mdEntry.concepto (libre)      โ”‚
   โ”‚  โ””โ”€โ”€ NO โ†’ continรบa al nivel 3 โ†“                                 โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                   โ”‚
                                   โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚  NIVEL 3: SIN CLASIFICAR (รบltimo recurso)                       โ”‚
   โ”‚  rubro          = 'SIN CLASIFICAR'                              โ”‚
   โ”‚  concepto_libre = obs.substring(0, 200)                         โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Mapeo de provincia (paso paralelo)

// JavaScript en sync.php / sync_bulk.php
const cajero    = cajaMap[`${empresa}-${cajaNro}`];
const provincia = CAJERO_PROV[cajero?.toLowerCase()?.trim()] || 'OTRAS';

// CAJERO_PROV es un objeto hardcoded:
const CAJERO_PROV = {
  'lar-cajero1': 'LA RIOJA',
  'sgo-cajero2': 'SANTIAGO DEL ESTERO',
  'cat-cajero3': 'CATAMARCA',
  // โ€ฆ
};
โš ๏ธ ARQ-03 ยท CAJERO_PROV hardcoded
El mapeo estรก duplicado entre sync.php y sync_bulk.php. Cada cajero nuevo requiere editar 2 archivos. Propuesta en ยง11: tabla DB caja_cajero_provincia editable desde UI.

๐Ÿ”ฅ Bug histรณrico (FIXED v2.0.2)

๐Ÿ› El nivel 2 nunca funcionรณ hasta v2.0.2 (2026-05-21)

El parser parseMovDetalleMap buscaba 'ME' en la columna 6 (Tipo = letra del comprobante A/B/C/P/E) cuando debe buscarlo en la columna 5 (Comprobante).

Impacto en los datos cargados con versiones anteriores:

  • 2024: 1.676 MEs reales no se procesaron (de 5.208 filas).
  • 2025: 3.850 MEs reales no se procesaron (de 12.162 filas).

Toda la clasificaciรณn dependรญa 100% de GastosTipificados; lo que no estaba ahรญ caรญa directamente como SIN CLASIFICAR. Re-sincronizar los meses afectados para que el nivel 2 funcione correctamente.

Mรฉtricas en log del bulk

GT: 185 | MD: 0 | SIN CLASIF: 0 | Sin ME: 37 | Otros meses: 2336 | Total: 278
  • GT: clasificadas por nivel 1 (GastosTipificados)
  • MD: clasificadas por nivel 2 (MovimientoDetalle + Conceptos)
  • SIN CLASIF: nivel 3 (rubro = 'SIN CLASIFICAR')
  • Sin ME: filas de EgresosDetalle sin nยบ ME extraรญble
  • Otros meses: filas de archivos compartidos filtradas por fecha
  • Total: filas finalmente insertadas en caja_gastos

4.4 ๐Ÿ—„๏ธ Schema v2 detallado

Vista global de las 10 tablas

                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                โ”‚   caja_sincronizaciones      โ”‚ control
                โ”‚   PK id ยท UK (mes, anio)     โ”‚
                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚ 1
                               โ”‚
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚ N                     โ”‚ N                       โ”‚ N
       โ–ผ                       โ–ผ                         โ–ผ
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚ caja_gastos  โ”‚  โ”‚   caja_raw_*  (6 tab.) โ”‚  โ”‚ caja_audit_log   โ”‚
 โ”‚ (derivada)   โ”‚  โ”‚   - egresos_caja       โ”‚  โ”‚ (eventos sync)   โ”‚
 โ”‚              โ”‚  โ”‚   - egresos_detalle    โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚   - gastos_tipificados โ”‚
                   โ”‚   - movimiento_detalle โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                   โ”‚   - movimiento_resumidoโ”‚  โ”‚ caja_cajeros     โ”‚
                   โ”‚   - conceptos          โ”‚  โ”‚ (denormalizaciรณn)โ”‚
                   โ”‚     (catรกlogo persist) โ”‚  โ”‚ rebuilt post-syncโ”‚
                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Helpers PHP en api/_schema.php

caja_ensure_schema(PDO $pdo)
    // Crea las 10 tablas con CREATE IF NOT EXISTS
    // Aplica ALTER TABLE defensivos en try/catch para migraciones idempotentes

caja_rebuild_cajeros(PDO $pdo)
    // Reconstruye caja_cajeros desde caja_gastos JOIN caja_raw_egresos_caja
    // Llamado al finalizar cada sync (save.php finalizar=true)

caja_audit_log(PDO $pdo, ?int $syncId, string $evento, ?string $tabla, ?int $filas, ?string $msg)
    // INSERT en caja_audit_log
    // try/catch silencioso: si falla no rompe el sync

Tipos de tablas y su rol

TablaRolTruncate en sync?Particionada por
caja_sincronizacionesControlNo (idempotente)โ€”
caja_gastosDerivada para dashboardNo (por sync_id)sync_id
caja_raw_egresos_cajaSnapshot ExcelNo (desde v2)sync_id
caja_raw_egresos_detalleSnapshot ExcelNosync_id
caja_raw_gastos_tipificadosSnapshot ExcelNosync_id
caja_raw_movimiento_detalleSnapshot ExcelNosync_id
caja_raw_movimiento_resumidoSnapshot ExcelNosync_id
caja_raw_conceptosโ˜… Catรกlogo persistenteSรณlo si se sube nuevoโ€”
caja_cajerosDenormalizaciรณnNo (rebuilt post-sync)โ€”
caja_audit_logAuditorรญaNo (append-only)โ€”

4.5 ๐Ÿ“ค Sync individual (sync.php ยท 37 KB)

Flujo

 Usuario          Browser (sync.php)              Server (api/*.php)
   โ”‚                       โ”‚                              โ”‚
   โ”‚ click "Cargar mes"    โ”‚                              โ”‚
   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚                              โ”‚
   โ”‚                       โ”‚ Dropzone 6 archivos:         โ”‚
   โ”‚ drag 6 Excel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚  ยท EgresosCaja               โ”‚
   โ”‚                       โ”‚  ยท ConceptosDeGastos         โ”‚
   โ”‚                       โ”‚  ยท EgresosDetalle            โ”‚
   โ”‚                       โ”‚  ยท GastosTipificados         โ”‚
   โ”‚                       โ”‚  ยท MovimientoDetalle         โ”‚
   โ”‚                       โ”‚  ยท MovimientoResumido (opt)  โ”‚
   โ”‚                       โ”‚                              โ”‚
   โ”‚ click "Procesar"      โ”‚                              โ”‚
   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚                              โ”‚
   โ”‚                       โ”‚ SheetJS parsea los 6 (browser)โ”‚
   โ”‚                       โ”‚ โ†’ arrays JS en memoria       โ”‚
   โ”‚                       โ”‚                              โ”‚
   โ”‚                       โ”‚ POST api/init.php            โ”‚
   โ”‚                       โ”‚ {mes, anio}                  โ”‚
   โ”‚                       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
   โ”‚                       โ”‚                              โ”‚ caja_ensure_schema()
   โ”‚                       โ”‚                              โ”‚ INSERT caja_sincronizaciones
   โ”‚                       โ”‚                              โ”‚ โ†’ sync_id
   โ”‚                       โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
   โ”‚                       โ”‚                              โ”‚
   โ”‚                       โ”‚ โ•”โ•โ•โ•โ• POST save_raw.php ร— 6 โ•โ•โ•โ•โ•—
   โ”‚                       โ”‚ โ•‘ {tabla, sync_id, rows: 300}   โ•‘
   โ”‚                       โ”‚ โ•‘ INSERT a la tabla raw         โ•‘
   โ”‚                       โ”‚ โ•‘                               โ•‘
   โ”‚                       โ”‚ โ•‘ Pipeline 3-tier en cliente:   โ•‘
   โ”‚                       โ”‚ โ•‘   1. Detectar gastos          โ•‘
   โ”‚                       โ”‚ โ•‘   2. Match GT por (s,n,e)    โ•‘
   โ”‚                       โ”‚ โ•‘   3. Fallback MD+Conceptos    โ•‘
   โ”‚                       โ”‚ โ•‘   4. SIN CLASIFICAR           โ•‘
   โ”‚                       โ”‚ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   โ”‚                       โ”‚                              โ”‚
   โ”‚                       โ”‚ POST save.php ร— N (caja_gastos)โ”‚
   โ”‚                       โ”‚ {sync_id, rows: 300}         โ”‚
   โ”‚                       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
   โ”‚                       โ”‚                              โ”‚
   โ”‚                       โ”‚ POST save.php {finalizar:1}  โ”‚
   โ”‚                       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
   โ”‚                       โ”‚                              โ”‚ caja_rebuild_cajeros()
   โ”‚                       โ”‚                              โ”‚ caja_audit_log()
   โ”‚                       โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค UPDATE estado='completado'
   โ”‚                       โ”‚                              โ”‚
   โ”‚ โœ… "Sync OK"          โ”‚                              โ”‚
   โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                              โ”‚

4.6 ๐Ÿ“ฆ Sync bulk multi-mes (sync_bulk.php ยท 57 KB)

UI mรกs sofisticada: detecta automรกticamente los meses contenidos en cada Excel y los procesa secuencialmente.

Algoritmo

  1. Usuario sube 1 archivo por tipo (cada uno puede contener varios meses).
  2. SheetJS parsea todos.
  3. JS escanea las fechas y detecta los meses รบnicos.
  4. Genera una "cola" de meses a procesar.
  5. Por cada mes: filtra las filas de cada Excel que pertenecen a ese mes, ejecuta el pipeline 3-tier, persiste con un sync_id dedicado.
  6. Al final: cada mes queda self-contained en la BD (eliminar uno no afecta los demรกs).
โœ… Bug histรณrico resuelto en v2.0.1
Antes de v2.0.1 los archivos compartidos (GT/MD/MR) quedaban todos con el sync_id del primer item del bulk โ†’ si eliminabas ese perรญodo se perdรญa la raw histรณrica de los demรกs. Fix: cada item del bulk filtra por fecha y persiste solo sus filas linkeadas a su propio sync_id.

Variables globales del JS del bulk

// Estado global (antes era const local โ†’ bug v2.0.1)
let cajaRowsGlobal = null;        // EgresosCaja parseado
let detalleRowsGlobal = null;     // EgresosDetalle
let tipificadosRowsGlobal = null;
let movDetalleRowsGlobal = null;
let movResumidoRowsGlobal = null;
let conceptosRowsGlobal = null;
let conceptosMap = {};            // Map para nivel 2
let CAJERO_PROV = { ... };        // โš ๏ธ Duplicado con sync.php

4.7 ๐Ÿฆ Submรณdulo Tesorerรญa (5 pestaรฑas)

KPIs ejecutivos (siempre visibles)

KPICรกlculoFuente
IngresosSUM(total_ingresos)caja_raw_egresos_caja
EgresosSUM(total_egresos)idem
Flujo NetoIngresos โˆ’ Egresoscalculado
Ratio Ing/EgrIngresos / Egresos (semรกforo)calculado
Saldo Finaltotal_saldo_final del รบltimo mes/mes filtradocaja_raw_egresos_caja

Pestaรฑa 1 ยท ๐Ÿ’ฐ Flujo de Caja

Endpoint: api/flujo.php?anio=N

  • Bar chart Ingresos (verde) vs Egresos (rojo) por mes + lรญnea Flujo Neto.
  • Stacked bar Saldo Final = Efectivo + Cheques de terceros, por mes.
  • Tabla detallada Categorรญas ร— Meses con secciones โž• INGRESOS y โž– EGRESOS.

Pestaรฑa 2 ยท ๐Ÿ’ณ Cobros & Pagos

Endpoint: api/cobros.php?anio=N

  • Donut Cobros por forma (Efectivo / Cheques / Transferencias / Venta cheques).
  • Stacked bar Contado vs Cta Cte por mes.
  • Donut Pagos por forma (vista anual).
  • Tabla Top 20 cuentas acreedoras con forma de pago dominante (sub-query).

Pestaรฑa 3 ยท ๐ŸŽฏ Origen de Cobros

Endpoint: api/origen.php?anio=N

  • Donut distribuciรณn anual por origen con % en leyenda.
  • Stacked bar estado de pago (PAGADO / PENDIENTE / PARCIAL).
  • Tabla Top 25 cuentas que mรกs cobran.

Pestaรฑa 4 ยท ๐Ÿฆ Cajas & Cheques (Mayo 2026)

Endpoint: api/cajas.php?anio=N

  • Bar + lรญnea: promedio mensual y mรกximo de cheques en cartera.
  • Lรญnea apilada 90 dรญas: saldo diario consolidado (efectivo + cheques).
  • Tabla Top 30 cajeros con eficiencia (neto coloreado).
  • Tabla saldo por caja fรญsica.

Pestaรฑa 5 ยท โš ๏ธ Alertas (Mayo 2026)

Endpoint: api/alertas.php?anio=N

  • Anticipos: entregados vs rendidos vs pendiente acumulado.
  • Runway defensivo (sin ingresos) vs estresado (-70% ingresos).
  • Anulados por mes (cantidad + monto).
  • Diferencia de cotizaciรณn (exposiciรณn USD).

Niveles de alerta del runway

Meses runwayNivelColor
< 1๐Ÿšจ Crรญticorojo
1 โ€“ 3๐Ÿ”ถ Naranjarojo
3 โ€“ 6โš ๏ธ Amarilloazul
โ‰ฅ 6โœ… Verdeverde

4.8 ๐Ÿ“Š Submรณdulo Finanzas (3 pestaรฑas)

Pestaรฑa 1 ยท ๐Ÿ“‘ Impuestos

Endpoints: api/iva.php ยท api/percepciones.php

  • IVA Crรฉdito: viene de caja_raw_movimiento_resumido. Discrimina por tasa (21%, 27%, 10.5%, otras).
  • IVA Dรฉbito: aproximado como subtotalNeto ร— 0.21 desde tabla ventas.
  • Saldo = Dรฉbito โˆ’ Crรฉdito. Positivo = a pagar, negativo = a favor.
  • Percepciones: per_ibrutos + per_3337 + per_5329 + perc_munic desde caja_raw_gastos_tipificados.

Pestaรฑa 2 ยท ๐Ÿข Costos & Proveedores

Endpoints: api/centros_costo.php ยท api/proveedores.php

  • Centros de Costo: bar horizontal Top 10 + stacked Top 6 ร— meses.
  • Top Proveedores: total facturado, neto gravado, comprobantes, รบltima compra, plazo promedio, forma de pago dominante.
  • Mรฉtricas de concentraciรณn: top10_pct, top20_pct.

Pestaรฑa 3 ยท ๐Ÿ“Š Rentabilidad

Endpoint: api/cruce_ventas.php?anio=N

Ventas netas     = SUM(subtotalNeto) FROM ventas
Ventas reales    = SUM(subtotalNeto WHERE dsArticulo IS NOT NULL)
Costo mercaderรญa = SUM(preciocomprant * cantidadesTotal)
Peso (ton)       = SUM(cantidadesTotal * peso) / 1000

Margen bruto     = Ventas reales โˆ’ Costo mercaderรญa
Margen limpio    = Margen bruto โˆ’ Gastos de Caja totales
% Margen limpio  = Margen limpio / Ventas reales

Costo logรญstico  = SUM(monto WHERE rubro LIKE %COMBUSTIBLE/FLETE/TRANSPORTE/...)
Costo admin      = SUM(monto WHERE rubro LIKE %SUELDO/SERVICIO/ALQUILER/...)

$/Tonelada       = Costo logรญstico / Peso (ton)
Admin/Venta      = Costo admin / Ventas netas  (%)

Heurรญsticas de clasificaciรณn de rubros

$rubrosLogisticos = ['COMBUSTIBLE','FLETE','TRANSPORTE','LUBRICANTE','PEAJE','GOMERIA','REPUESTO'];
$rubrosAdmin      = ['SALARIO','SUELDO','HONORARIO','SERVICIO','TELEFONIA','INTERNET','ALQUILER','SEGURO','BANCO','IMPUESTO'];

// Se hace UPPER(rubro) LIKE '%KEY%' por cada palabra
โš ๏ธ Limitaciรณn de las heurรญsticas
Si el ERP usa nombres de rubros que no encajan en estas keywords, no se clasifican. Editar manualmente los arrays cuando aparezcan rubros nuevos importantes.

4.9 ๐Ÿ’ธ Submรณdulo Gastos (el dashboard original)

Endpoint: api/data.php?anio=N&mes=M&prov=P&rubro=R

Dashboard

  • Tabulator dataTree: รกrbol RUBRO โ–ธ CONCEPTO con expansiรณn y suma.
  • Chart.js: bar chart por mes con stacked rubros.
  • Filtros: aรฑo, mes (botonera), provincia (LA RIOJA / SGO / CAT / OTRAS), rubro.
  • KPI: gasto total, # comprobantes, # cajeros activos, promedio diario.

Queries clave

-- Agregado RUBRO ร— CONCEPTO ร— MES (Mayo 2024 - Diciembre 2025 actual)
SELECT
  rubro,
  concepto_libre AS concepto,
  mes,
  SUM(monto) AS monto,
  COUNT(*) AS lineas
FROM caja_gastos
WHERE anio = ?
  AND ($mes = 0 OR mes = $mes)
  AND ($prov = '' OR provincia = $prov)
GROUP BY rubro, concepto, mes
ORDER BY rubro, concepto, mes;

4.10 ๐Ÿ› Bugs histรณricos resueltos (changelog)

VersiรณnFechaBugImpacto
v2.0.22026-05-21 parseMovDetalleMap: leรญa col 6 en vez de col 5 para detectar MEs. 5.526 MEs perdidos en 2024+2025 caรญan como SIN CLASIFICAR.
v2.0.12026-05-21 Bulk sync: archivos compartidos GT/MD/MR quedaban con el sync_id del primer item. Eliminar un perรญodo borraba data raw histรณrica de otros.
v2.0.12026-05-21 Scope bug: cajaRows era const local a importarTodo(). procesarMes() no podรญa acceder โ†’ filas de EgresosCaja no se persistรญan.
v2.0.02026-05-21 caja_raw_egresos_caja era catรกlogo (TRUNCATE per sync). Tesorerรญa sรณlo mostraba el รบltimo mes sincronizado. Migraciรณn automรกtica.

4.11 โš ๏ธ Hallazgos especรญficos del mรณdulo

IDHallazgoLรญnea/archivoPrioridad
CAJ-01 dashboard.jsx (22 KB) legado React quedรณ en producciรณn. caja/dashboard.jsx Alto
CAJ-02 reset_db.php sin auth โ€” cualquiera puede DROPear las 10 tablas. reset_db.php:1-3 (session_start comentado) Crรญtico
CAJ-03 SQL interpolation: "DELETE FROM $t WHERE sync_id=$sid" dentro de loop. reset_db.php:46-48 Alto
CAJ-04 CAJERO_PROV duplicado en sync.php y sync_bulk.php (mapa JS hardcoded). ~150 (sync) ยท ~310 (bulk) Medio
CAJ-05 Pipeline 3-tier corre en cliente (JS): bug histรณrico v2.0.2 muestra fragilidad. sync*.php Medio
CAJ-06 Heurรญsticas de "rubros logรญsticos/admin" como arrays hardcoded en PHP. finanzas/api/cruce_ventas.php Medio
CAJ-07 Cajeros sin matchear caen como provincia='OTRAS'. No hay alerta proactiva. sync.php Bajo
CAJ-08 IVA Dรฉbito aproximado al 21% genรฉrico โ€” no discrimina por tipo de comprobante. finanzas/api/iva.php Medio
CAJ-09 Sin FK declaradas โ€” eliminar manualmente con delete_period depende del cรณdigo. api/_schema.php Medio
CAJ-10 Catรกlogo articulos_raw_tipos_mercaderia y unidades_medida vacรญos en BD. BD Bajo

4.12 ๐Ÿš€ Propuestas especรญficas del mรณdulo

P-CAJ-1 ยท Tabla DB para CAJERO_PROV

CREATE TABLE caja_cajero_provincia (
  cajero       VARCHAR(100) PRIMARY KEY,
  provincia    VARCHAR(50)  NOT NULL,
  activo       TINYINT(1)   DEFAULT 1,
  notas        TEXT,
  creado_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- UI admin: /reportes/caja/admin/cajero_provincia.php
-- Pipeline lee de DB en vez de JS hardcoded

P-CAJ-2 ยท Mover pipeline 3-tier al backend

Actualmente el pipeline corre en JS (frontend). Esto:

  • Fragilidad: el bug histรณrico de col 5 vs 6 estuvo aรฑos sin detectarse.
  • Difรญcil testear unitariamente.
  • Pesa el browser (parsear 6 Excel grandes consume RAM).

Propuesta: upload de archivos crudos โ†’ save_raw.php los persiste tal cual โ†’ cron job nocturno reconstruye caja_gastos en PHP con tests unitarios. Esfuerzo: 1 semana.

P-CAJ-3 ยท Tabla DB para reglas de clasificaciรณn de rubros

CREATE TABLE caja_rubro_categoria (
  id         INT PK AI,
  pattern    VARCHAR(100) NOT NULL,   -- ej: "%COMBUSTIBLE%"
  categoria  ENUM('logistico','administrativo','operativo','otro'),
  prioridad  TINYINT DEFAULT 0,
  activo     TINYINT(1) DEFAULT 1
);
-- Reemplaza los arrays hardcoded en cruce_ventas.php
-- UI admin para editar sin tocar cรณdigo

P-CAJ-4 ยท FK CASCADE para reset_db.php mรกs simple

ALTER TABLE caja_gastos                  ADD FOREIGN KEY (sync_id) REFERENCES caja_sincronizaciones(id) ON DELETE CASCADE;
ALTER TABLE caja_raw_egresos_caja        ADD FOREIGN KEY (sync_id) REFERENCES caja_sincronizaciones(id) ON DELETE CASCADE;
-- ... etc para las 6 raw

-- Luego reset_db.php delete_period queda en 1 lรญnea:
DELETE FROM caja_sincronizaciones WHERE id = ?;
-- (las raw + gastos se borran solas)

P-CAJ-5 ยท IVA dรฉbito discriminado por tasa

Hoy: dรฉbito = ventas * 0.21 (genรฉrico).

Propuesta: usar tipoIvaArticulo de la tabla ventas (existe pero no se usa) para discriminar tasas. Si no existe, crearla.

P-CAJ-6 ยท Alertas proactivas en sync

  • Al finalizar sync, verificar que el % de SIN CLASIFICAR < 5%. Si no, mostrar warning en UI.
  • Verificar que cajeros nuevos sin provincia matched > 0 โ†’ email + UI badge.
  • Verificar que SUM(monto) del mes estรก en rango ยฑ50% del promedio histรณrico โ€” alerta de anomalรญa.

P-CAJ-7 ยท Eliminar dashboard.jsx

30 segundos. Backup si querรฉs (dashboard.jsx.bak.2026-05-21) y eliminarlo del doc root.

P-CAJ-8 ยท Vista materializada para Tesorerรญa

Los KPIs ejecutivos se recalculan en cada hit a /tesoreria/. Con cron diario:

CREATE TABLE caja_tesoreria_mensual_mv (
  anio        SMALLINT,
  mes         TINYINT,
  ingresos    DECIMAL(15,2),
  egresos     DECIMAL(15,2),
  saldo_final DECIMAL(15,2),
  dias_caja   INT,
  updated_at  TIMESTAMP,
  PRIMARY KEY (anio, mes)
);

-- Refresh cron diario 03:30:
INSERT INTO caja_tesoreria_mensual_mv ...
ON DUPLICATE KEY UPDATE ...;