5.1 🎯 Overview del módulo

El módulo más analítico. No tiene sync con API externa: trabaja sobre snapshots manuales de cartera (CSV/XLSX) y los combina con datos de venta y caja ya cargados para proyectar el flujo de los próximos 30/60/90 días.

Versión
v1.1.0
Reset DB + integración Caja v2
Tablas propias
4
+ leen ventas y caja_raw_egresos_caja
Tipos de cartera
3
cliente · proveedor · cheque
Cruce comercial
6 meses de venta por cliente
Calendario proyectado
4 sem
Próximas 4 semanas (cobranza + cheques − pagos)
Categorías de riesgo
5
critico_incobrable, incumplimiento, fantasma, creciente, sano

Preguntas que responde

  • ¿Cuánto cobro y pago en los próximos 7/15/30/60 días?
  • ¿Mi saldo proyectado a 30 días alcanza para cubrir pagos?
  • ¿Qué clientes son fantasmas (deben mucho y no compran hace meses)?
  • ¿Qué vendedor tiene la peor performance de cobranza?
  • ¿Qué proveedor concentra el mayor riesgo de pago?
  • ¿Cuántos cheques tengo en cartera, en qué banco, de qué clientes?
  • ¿Hay cheques vencidos sin presentar al cobro? (riesgo de prescripción)

5.2 📥 Datos de entrada (3 tipos)

TipoArchivo del ERPGranularidadTabla
cliente Cartera de clientes (CSV/XLSX) Una fila por cliente (aging por tramos) cashflow_cartera_clientes
proveedor Cartera de proveedores (CSV/XLSX) Una fila por comprobante (FC/NC/ND/AD/ME) cashflow_cartera_proveedores
cheque Cheques Recibidos (CSV/XLSX) Una fila por cheque cashflow_cheques_cartera

Detección de encoding del CSV

El ERP exporta CSVs en cp1252 con BOM raro. El JS detecta y aplica fallback:

let txt = new TextDecoder('utf-8', {fatal: false}).decode(buf);
// Heurística: si tiene caracteres mal codificados, probar Windows-1252
if (txt.includes('Â') || txt.includes('Ã')) {
  txt = new TextDecoder('windows-1252').decode(buf);
}

5.3 🗄️ Schema

Relación entre las 4 tablas

              ┌──────────────────────────────────────┐
              │      cashflow_sincronizaciones       │
              │  PK id · UK (tipo, fecha_corte)      │
              │  tipo ∈ {cliente, proveedor, cheque} │
              └─────────────────┬────────────────────┘
                                │ 1
                                │
              ┌─────────────────┼─────────────────────┐
              │ N               │ N                   │ N
              ▼                 ▼                     ▼
   ┌──────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
   │ cartera_clientes │ │ cartera_proveedores │ │  cheques_cartera │
   │  cliente_id      │ │  comprobante        │ │  numero_cheque   │
   │  saldo + 5 tramos│ │  fecha_vto · total  │ │  banco · estado  │
   │  vendedor · prov │ │  vencido · atraso   │ │  presentación    │
   └────────┬─────────┘ └──────────┬──────────┘ └────────┬─────────┘
            │                      │                     │
            │ JOIN id ▼            │                     │ JOIN id ▼
            ▼                      │                     ▼
   ┌──────────────────┐            │           ┌──────────────────┐
   │     ventas       │            │           │   ventas         │
   │  (cruce 6 meses) │            │           │  (concentración) │
   └──────────────────┘            │           └──────────────────┘
                                   │
                                   │
                                   ▼ extractos (futuro)
   ┌──────────────────┐  ┌──────────────────────────┐
   │ extractos_banc.  │  │ caja_raw_egresos_caja    │   ← lee
   │ (saldo bancario) │  │ (saldo caja físico)      │   ← Saldo Caja KPI
   └──────────────────┘  └──────────────────────────┘

5.4 📊 Dashboard index.php (51 KB)

10 secciones

┌─────────────────────────────────────────────────────────────┐
│ HEADER · selector de corte (dropdown con todos los snapshots)│
├─────────────────────────────────────────────────────────────┤
│ ⚡ 5 KPI CARDS principales                                  │
│  Saldo Caja Hoy · Cobranzas 30d · Pagos 30d                 │
│  Cheques Cartera · Saldo Proyectado 30d (con semáforo)      │
├─────────────────────────────────────────────────────────────┤
│ 🚨 ALERTAS AUTOMÁTICAS                                      │
│  · 🔴 Vencido a pagar > $150M                               │
│  · 🟡 Saldo proyectado < $10M                               │
├─────────────────────────────────────────────────────────────┤
│ 📊 AGINGS (2 columnas)                                      │
│  Cartera Clientes (8 tramos: 0/1-7/8-15/16-30/31-60/        │
│                          61-90/91-180/+180 días)            │
│  Cartera Proveedores (días de atraso)                       │
├─────────────────────────────────────────────────────────────┤
│ 📅 CALENDARIO SEMANAL (próximas 4 semanas)                  │
│  Cols: Cobranzas · Cheques · Pagos · Neto                   │
│  Saldo acumulado proyectado · semáforo crítico              │
├─────────────────────────────────────────────────────────────┤
│ 👥 TOP DEUDORES CLIENTES (Tabulator)                        │
│  Con cruce comercial: venta 6m, días sin comprar, NDCONs    │
│  Categorización automática (semáforo de riesgo)             │
├─────────────────────────────────────────────────────────────┤
│ 💸 PAGOS PRÓXIMOS / VENCIDOS (Tabulator)                    │
│  Vencidos (rojo) + futuros (verde) ordenables               │
├─────────────────────────────────────────────────────────────┤
│ 🧑‍💼 PERFORMANCE COBRANZA POR VENDEDOR                       │
│  Cartera total · vencido · % vencido (barra + semáforo)     │
├─────────────────────────────────────────────────────────────┤
│ 🧾 CHEQUES EN CARTERA (Tabulator)                           │
│  Nº cheque · banco · cliente emisor · presentación · días   │
│  Semáforo: rojo=vencido, ámbar≤7d, azul≤30d, verde>30d      │
├─────────────────────────────────────────────────────────────┤
│ 🏦 CONCENTRACIÓN BANCO · 👤 CONCENTRACIÓN CLIENTE EMISOR    │
│  (2 columnas) · alerta si cliente >50% es riesgo crítico    │
└─────────────────────────────────────────────────────────────┘

Estructura del JSON de api/data.php

{
  "ok": true,
  "corte": "2026-05-20",
  "cortes_dispo": ["2026-05-20", "2026-04-30", "2026-03-31"],
  "saldo_caja_hoy": 58000000,
  "clientes": {
    "kpi":          { total, d7, d15, d30, d60, vencido, clientes },
    "aging":        { "0_al_dia", "1_7", "8_15", "16_30", "31_60", "61_90", "91_180", "mas_180" },
    "top_deudores": [{cliente_nombre, vendedor, saldo, antiguedad,
                      categoria, accion, venta_6m, dias_sin_comprar, …}],
    "por_vendedor":  [...],
    "por_provincia": [...]
  },
  "proveedores": {
    "kpi":          { a_pagar, vencido, anticipos, ... },
    "aging":        { ... },
    "top":          [...],
    "pagos_futuros":[...],
    "vencidos":     [...]
  },
  "cheques": {
    "kpi":          { n_cartera, monto_cartera, n_vencidos, monto_vencidos,
                      monto_endosados, monto_rechazados },
    "lista":        [{numero_cheque, banco, fecha_presentacion, importe,
                      cliente_nombre, dias_a_presentar, ...}],
    "por_banco":    [{banco, n, monto}],
    "por_cliente":  [{cliente_nombre, cuit_cliente, n, monto}]
  },
  "calendario": [{semana, desde, hasta, cobranzas, cheques, pagos, neto}, ...]
}

5.5 🚨 Clasificación automática de riesgo (clientes)

// Pseudocódigo PHP en api/data.php
if ($antig > 180 && $saldo > 5_000_000)        $cat = 'critico_incobrable';
else if ($ndcons_monto > 0)                     $cat = 'critico_incumplimiento';
else if ($dias_sin_comprar > 90 && $saldo > 1M) $cat = 'fantasma';
else if ($saldo > $venta_6m / 6 * 3)            $cat = 'riesgo_creciente';
else if ($antig <= 30)                          $cat = 'sano';
else                                            $cat = 'normal';

Tabla de categorías y acciones sugeridas

CategoríaTriggerAcción sugeridaColor
critico_incobrable +180 d antig + $5M+ Provisionar como incobrable / Acción legal rojo
critico_incumplimiento Tiene NDCONs (cheque rechazado) Cortar crédito / Solo contado rojo
fantasma +90 d sin comprar + $1M+ Llamar / Verificar relación comercial ámbar
riesgo_creciente Saldo > 3 meses de venta promedio Limitar nuevos pedidos / Negociar plan ámbar
sano Antig ≤ 30 d Continuar normal verde

Casos reales detectados con datos cargados

ClienteCategoríaPor qué
LOS LEONES SRLcrítico_incumplimiento$2.6M en NDCONs (cheque rechazado)
VICTOR NICOLAS VEGAcrítico_incobrable460 d antigüedad + saldo $4.3M
BARRIONUEVO FERNANDOriesgo_crecienteSaldo 3× venta promedio mensual

5.6 📅 Calendario semanal de flujo

Lógica de estimación de cobranza por semana

// JavaScript en index.php (frontend)
// Aproximación a partir de los tramos del aging
if (w == 0) cobr = cli.d7;                       // todo lo que vence en 7d → semana 1
if (w == 1) cobr = cli.d15 / 2;                  // mitad de 8-15d → semana 2
if (w == 2) cobr = cli.d15 / 2 + cli.d30 / 4;    // resto + cuarto de 30d
if (w == 3) cobr = cli.d30 / 4 + cli.d30 / 4;    // dos cuartos

Cálculo del neto semanal

neto_semana = cobranzas_estim + cheques_a_presentar − pagos_exactos

// Saldo proyectado al final de cada semana:
saldo_w1 = saldo_caja_hoy + neto_w1
saldo_w2 = saldo_w1       + neto_w2
saldo_w3 = saldo_w2       + neto_w3
saldo_w4 = saldo_w3       + neto_w4
💡 Pagos sí son exactos
Los pagos a proveedores tienen fecha_vto real en cada comprobante. Sólo las cobranzas se estiman (porque el CSV de clientes viene agrupado por tramos, no fecha por fecha).

5.7 🔄 Cruce comercial (el "superpoder")

Para cada cliente con deuda, se extrae su historial de los últimos 6 meses desde la tabla ventas:

SELECT v.idCliente,
       COUNT(DISTINCT v.sync_id)             AS meses_activo_6m,
       SUM(v.subtotalNeto)                   AS venta_6m,
       MAX(v.fechaComprobate)                AS ultima_compra,
       DATEDIFF(?, MAX(v.fechaComprobate))   AS dias_sin_comprar,
       SUM(CASE WHEN v.dsArticulo IS NULL THEN v.subtotalNeto ELSE 0 END) AS ndcons_monto,
       SUM(CASE WHEN v.dsArticulo IS NULL THEN 1 ELSE 0 END)              AS ndcons_n
FROM ventas v
WHERE v.sync_id IN (últimos 6 meses)
  AND v.anulado = 'NO' AND v.idCliente IS NOT NULL
GROUP BY v.idCliente;

Esto enriquece a cada deudor con:

  • Cuántos meses compró de los últimos 6.
  • Cuánto vendió en total.
  • Última fecha de compra y días desde entonces.
  • NDCONs (cheques rechazados, intereses) → flag de incumplimiento.

KPIs de cheques

SELECT
  SUM(CASE WHEN estado='EN CARTERA' THEN 1 ELSE 0 END)        AS n_cartera,
  SUM(CASE WHEN estado='EN CARTERA' THEN importe ELSE 0 END)  AS monto_cartera,
  SUM(CASE WHEN estado='EN CARTERA' AND fecha_presentacion < :corte
           THEN importe ELSE 0 END)                            AS monto_vencidos,
  SUM(CASE WHEN estado='PAGO A TERCEROS' THEN importe ELSE 0 END) AS monto_endosados,
  SUM(CASE WHEN estado='RECHAZADO'       THEN importe ELSE 0 END) AS monto_rechazados
FROM cashflow_cheques_cartera
WHERE fecha_corte = :corte;

monto_vencidos mide riesgo operativo: cheques EN CARTERA cuya fecha de presentación ya pasó. Cada uno es plata estancada (o peor, riesgo de prescripción a 30 días post-vencimiento).

5.8 ⚠️ Hallazgos específicos del módulo

IDHallazgoPrioridad
CSH-01 reset_db.php y delete-sync.php sin auth — borrar todo es trivial. Crítico
CSH-02 Heurística de cobranza semanal (cuartos arbitrarios) — no usa fechas exactas de FCs. Medio
CSH-03 Cheques endosados (PAGO A TERCEROS) no se cruzan con cancelación del FC del proveedor. Medio
CSH-04 cashflow_extractos_bancarios creada pero sin sync ni UI — schema sin uso. Medio
CSH-05 Categorías de riesgo hardcodeadas en PHP con umbrales mágicos ($5M, 180d, $1M). Medio
CSH-06 Match cliente↔venta por idCliente rígido — si el ERP cambió el nombre o duplicó el cliente, falla. Bajo
CSH-07 Cliente duplicado en el ERP aparece como 2 entidades en el dashboard. Bajo
CSH-08 Sin alerta de cheques próximos a prescribir (30 d post-vencimiento). Medio

5.9 🚀 Propuestas específicas del módulo

P-CSH-1 · Reglas de riesgo en tabla DB

CREATE TABLE cashflow_reglas_riesgo (
  id           INT PK AI,
  categoria    VARCHAR(40),
  prioridad    TINYINT,                -- orden de evaluación
  condicion    VARCHAR(255),            -- DSL simple: "antig>180 AND saldo>5000000"
  accion       VARCHAR(255),
  color        VARCHAR(7),              -- #ef4444
  activo       TINYINT(1) DEFAULT 1
);

-- Engine de evaluación en PHP que parsea el DSL
-- UI admin para editar umbrales sin tocar código

P-CSH-2 · Sync de extractos bancarios

La tabla cashflow_extractos_bancarios ya existe (1.744 filas). Crear sync.php?tipo=extracto para alimentarla y agregar al dashboard:

  • Conciliación: egr_transferencias de caja vs debito del extracto.
  • Detección de movimientos no contabilizados.
  • Saldo bancario en KPI principal (hoy solo está saldo de caja).

P-CSH-3 · Cruce de cheques endosados

Cuando un cheque cambia a PAGO A TERCEROS:

  • Match con un FC de proveedor por monto + fecha.
  • Descontar de "pagos a proveedor" (hoy se ignora el endoso).
  • Mostrar trazabilidad en el detalle del cheque y del FC.

P-CSH-4 · Forecast de cobranza con regresión

En vez de "cuartos arbitrarios", usar histórico real de cobranza:

  • De los cortes pasados, calcular qué % de cada tramo se cobró en cada semana siguiente.
  • Ajustar por estacionalidad (mes calendario) y vendedor.
  • Devolver intervalo de confianza (±15%).

P-CSH-5 · Alerta de cheques próximos a prescribir

-- Cheques EN CARTERA presentados hace > 0 días y < 30 días
SELECT COUNT(*), SUM(importe)
FROM cashflow_cheques_cartera
WHERE estado = 'EN CARTERA'
  AND fecha_corte = (SELECT MAX(fecha_corte) FROM cashflow_sincronizaciones)
  AND fecha_presentacion < CURRENT_DATE
  AND fecha_presentacion >= DATE_SUB(CURRENT_DATE, INTERVAL 30 DAY);

-- → KPI rojo en dashboard: "X cheques en zona de prescripción"

P-CSH-6 · Snapshot diff entre cortes

Cuando hay 2+ cortes, mostrar evolución MoM por cliente:

  • Clientes que subieron de saldo entre cortes (riesgo creciente).
  • Clientes que cancelaron deuda (action: agradecer + ofrecer límite mayor).
  • Clientes nuevos vs salidos de la cartera.

P-CSH-7 · Email diario automático del estado

Cron 06:00 → enviar email con:

  • Saldo proyectado a 30 días (con semáforo).
  • Top 5 deudores nuevos / con saldo creciente.
  • Cheques que se presentan hoy.
  • Pagos del día (vencidos + nuevos vencimientos).