12.1 📍 Hoy: dónde estamos

Hosting
Hostinger shared
PHP 8.x + MariaDB 11.8
BD size
~900 MB
Crecimiento ~50 MB/mes
Filas ventas
515 K
~23 K filas/mes promedio
Usuarios concurrentes
~5
Estimado · uso interno
P95 dashboard
~2 s
Estimado · sin medición real
Costo mensual
~USD 5
Hostinger Business

Capacidad headroom (Hostinger Business típico)

  • Storage: 200 GB → ocupamos ~1% → headroom enorme.
  • RAM PHP: 1.5 GB típicamente → suficiente para queries actuales.
  • BD: sin límite duro, pero "fair use" sugiere <5 GB.
  • Procesos concurrentes: ~25 PHP-FPM workers → suficiente para 5 users.
  • Outbound HTTP: ~30 conexiones simultáneas (afecta sync de ChessERP).

12.2 📈 Proyección a 2 años (Mayo 2028)

Asumiendo crecimiento moderado del negocio y uso del sistema:

Métrica Hoy (May 26) +1 año +2 años Notas
Filas ventas 515 K ~830 K ~1.2 M +20% crecimiento anual
Filas caja_raw_egresos_detalle 262 K ~420 K ~600 K Crece con volumen operativo
BD total ~900 MB ~1.6 GB ~2.4 GB Dentro de Hostinger
SKUs catálogo 7.870 ~9.000 ~10.500 Crecimiento lento
Clientes activos ~3.000 ~4.500 ~6.000 Si Zonas Áridas abre sucursales
Users concurrentes ~5 ~15 ~30 Si más áreas usan el sistema
Períodos cargados 22 34 46 Lineal
P95 dashboard (sin acción) ~2 s ~3.5 s ~6 s ⚠️ Degradación lineal sin MVs
P95 dashboard (con MVs) ~50 ms ~80 ms ~120 ms Estable con vistas materializadas

Cuándo cada componente "se rompe" sin optimizar

  • BD < 3 GB → MariaDB sigue rápida con índices apropiados.
  • BD entre 3-10 GB → empezar a particionar tablas históricas.
  • BD > 10 GB → considerar migrar a VPS con MySQL/Postgres dedicado.
  • 5 → 50 users concurrentes → Hostinger todavía aguanta con cache HTTP.
  • 50+ users concurrentes → migrar a VPS con Nginx + PHP-FPM tuned.
  • Dashboards > 5s consistentes → MVs son obligatorias.

12.3 🚧 Cuellos de botella identificados

#ComponenteCuando se sienteMitigación
1 data_api.php · ~20 queries por dashboard Cuando ventas > 1M filas Vistas materializadas + cache HTTP (F2-1, F2-3)
2 JOIN ventas × articulos_raw con CAST Si se quita CAST + se agrega índice, OK por 5 años PERF-03 + PERF-02
3 Sync de ChessERP HTTP largas Ya hoy: meses pesados pueden timeout Hostinger workers límite + retry logic (RES-01)
4 Parseo Excel en cliente (SheetJS) Si archivo > 50 MB browser puede colgarse Mover parseo a backend (P-CAJ-2)
5 dashboard.php inyecta datos en HTML Cuando Cliente/Artículo > 5000 filas Lazy load por tab via fetch (PERF-06)
6 Sin pool de conexiones BD Con > 20 users concurrentes PHP-FPM tuning o migrar a Nginx/ProxySQL
7 BD compartida con sitio público Picos de tráfico web compiten Separar instancias (Fase 2+)
8 UPDATE en cada hit de listas/index.php YA hoy desperdicia recursos Trigger o cron (P-LIS-2)
9 Pipeline 3-tier corre en cliente Archivos > 20 MB ya son lentos Mover al backend (P-CAJ-2)
10 Sin CDN para Tabulator/Chart.js Cada hit baja ~700 KB de librerías Self-host con headers Cache-Control

12.4 🗄️ Estrategias para escalar la BD

Nivel 1 · Índices (gratis, inmediato)

-- PERF-02: índice compuesto
ALTER TABLE ventas ADD INDEX idx_sync_anulado_articulo
  (sync_id, anulado, dsArticulo(60));

-- Para drill-downs
ALTER TABLE ventas ADD INDEX idx_sync_supervisor (sync_id, dsSupervisor);
ALTER TABLE ventas ADD INDEX idx_sync_cliente    (sync_id, idCliente);
ALTER TABLE ventas ADD INDEX idx_sync_proveedor  (sync_id, proveedor(100));

-- Para caja/finanzas
ALTER TABLE caja_raw_gastos_tipificados ADD INDEX idx_anio_rubro
  ((YEAR(fecha_comp)), rubro_id);                  -- functional index MariaDB 10.6+

Nivel 2 · Vistas materializadas (cron diario)

Recalcular cada mañana → todo el día queda servido desde la MV (50 ms vs 2 s).

CREATE TABLE ventas_mensual_supervisor_mv (
  sync_id          INT,
  dsSupervisor     VARCHAR(100),
  ventas_netas     DECIMAL(15,2),
  ventas_reales    DECIMAL(15,2),
  costo_total      DECIMAL(15,2),
  contribucion     DECIMAL(15,2),
  bultos_total     DECIMAL(15,2),
  peso_kg_total    DECIMAL(15,2),
  clientes_unicos  INT,
  total_comprobantes INT,
  refreshed_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (sync_id, dsSupervisor)
);

CREATE TABLE ventas_mensual_supervisor_mv … (idem para vendedor, depósito, cliente, artículo, proveedor);

-- Refresh nightly:
TRUNCATE ventas_mensual_supervisor_mv;
INSERT INTO ventas_mensual_supervisor_mv ...;

Nivel 3 · Particionado por sync_id

-- Cuando ventas supere 2M filas
ALTER TABLE ventas
PARTITION BY RANGE (sync_id) (
  PARTITION p2024 VALUES LESS THAN (15),    -- syncs id 1-14
  PARTITION p2025 VALUES LESS THAN (28),
  PARTITION p2026 VALUES LESS THAN (50),
  PARTITION pmax  VALUES LESS THAN MAXVALUE
);

-- Beneficio: queries con WHERE sync_id IN (...) sólo tocan particiones relevantes
-- DROP de un sync borra una partición (O(1)) en vez de DELETE WHERE

Nivel 4 · Archivado de períodos viejos

-- Mover ventas de más de 3 años a tabla archivo
CREATE TABLE ventas_archivo LIKE ventas;
ENGINE = Archive;  -- MariaDB · ~80% menos espacio, solo INSERT/SELECT

INSERT INTO ventas_archivo SELECT * FROM ventas
WHERE sync_id IN (SELECT id FROM sincronizaciones WHERE anio < YEAR(NOW()) - 3);

DELETE FROM ventas WHERE sync_id IN (...);
-- Tabla "ventas" queda ágil; lecturas de archivo via UNION

Nivel 5 · Réplica de lectura (cuando > 50 users)

   ┌──────────────┐
   │  App (PHP)   │
   └──────┬───────┘
          │
          ├──── INSERT/UPDATE ────► ┌─────────────────┐
          │                          │ MariaDB MASTER  │
          │                          │ (escrituras)    │
          │                          └─────────────────┘
          │                                  │
          │                                  │ async replication
          │                                  ▼
          └──── SELECT ───────────► ┌─────────────────┐
                                    │ MariaDB SLAVE   │
                                    │ (sólo lecturas) │
                                    └─────────────────┘

Nivel 6 · Migrar a Postgres + Citus / TimescaleDB (long-term)

Si se llega a >100M filas o >100 users → Postgres con extensiones de time-series.

12.5 ⚙️ Estrategias para escalar la app

1 · Opcode cache (gratis)

; php.ini
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
opcache.preload = /home/u120688891/public_html/reportes/_shared/preload.php

; Verificar:
opcache_get_status();

Mejora del orden 30-40% en tiempo de respuesta sin tocar código.

2 · HTTP Cache + ETag

Ver §11 F2-3. Reduce carga al backend en 50-80% para endpoints JSON.

3 · Pool de PDO singleton

Ya existe en db_reportes.php. Asegurar que se usa en TODOS los archivos (algunos crean PDO ad-hoc — ver reset_db.php).

4 · Lazy loading de tabs

// dashboard.php · cada tab carga sus datos AL hacer click por primera vez
const tabState = { vend: 'pending', sup: 'pending', dep: 'pending',
                   cli: 'pending', art: 'pending', prov: 'pending' };

function loadTabIfNeeded(tabKey) {
    if (tabState[tabKey] !== 'pending') return;
    tabState[tabKey] = 'loading';
    fetch(`/reportes/venta/api/tab.php?tab=${tabKey}&sync=${syncId}&sucursal=${sucursal}`)
        .then(r => r.json())
        .then(data => {
            initTabulator(tabKey, data);
            tabState[tabKey] = 'loaded';
        });
}

// Reduce el HTML inicial de 2 MB a ~300 KB
// Mejor First Contentful Paint (FCP) en mobile

5 · Workers para tareas pesadas

El sync de ChessERP bloquea la UI. Mover a background:

// /reportes/_shared/queue.php (file-based simple)
function queue_push(string $job, array $payload): int {
    $id = uniqid();
    file_put_contents(__DIR__ . "/jobs/$id.json", json_encode([
        'job' => $job, 'payload' => $payload,
        'created_at' => date('c'),
    ]));
    return $id;
}

// Cron cada 1 min procesa los jobs:
* * * * * php /reportes/_shared/queue_worker.php

// UI hace polling al endpoint de status del job
GET /reportes/_shared/job_status.php?id=abc123
→ {status: 'running', progress: 67, message: 'Lote 5 de 8'}

12.6 🎨 Estrategias para escalar el frontend

1 · Self-host de librerías CDN

/reportes/_shared/vendor/
├── tabulator/
│   ├── tabulator.min.js   (~250 KB)
│   └── tabulator.min.css  (~80 KB)
├── chartjs/
│   └── chart.umd.js       (~210 KB)
└── sheetjs/
    └── xlsx.full.min.js   (~900 KB)

// Servir con Cache-Control: public, max-age=31536000, immutable
// Browsers cachean para siempre → reduce hits a CDN externo

2 · Modularización del JS

El dashboard.php de venta tiene ~2000 líneas de JS inline. Extraer a módulos ES6:

/reportes/venta/assets/js/
├── app.js              ← bootstrap general (existe)
├── formatters.js       ← fmtMoney, fmtPct, fmtTons
├── tabs.js             ← openTab, fullscreen
├── rent-tabs.js        ← inicialización de los 6 Tabulator
├── charts.js           ← Chart.js wrappers
└── filters.js          ← cross-filter modal

// dashboard.php
<script type="module" src="assets/js/dashboard-main.js"></script>

3 · Virtual DOM-free para listas grandes

Tabulator ya usa virtual scroll, pero asegurar que virtualDom: true esté activado en todas las tablas.

4 · Service Worker para offline-friendly

// /reportes/sw.js
self.addEventListener('fetch', e => {
  const url = new URL(e.request.url);
  // Cache librerías estáticas
  if (url.pathname.startsWith('/reportes/_shared/vendor/')) {
    e.respondWith(
      caches.open('vendor-v1').then(cache =>
        cache.match(e.request).then(r => r || fetch(e.request).then(resp => {
          cache.put(e.request, resp.clone());
          return resp;
        }))
      )
    );
  }
});

5 · Imágenes (logos) optimizadas

Los SVG ya son ligeros. Si se agregan fotos / banners → WebP + lazy loading.

12.7 ☁️ Cuándo migrar de Hostinger

Triggers para migrar a VPS

TriggerCuándoTipo recomendado
BD > 5 GB Estimado año 3 VPS con 4-8 GB RAM (DigitalOcean / Hetzner / Contabo)
P95 > 5s consistente Si no se aplican MVs VPS para tuning fino de MySQL my.cnf
> 30 users concurrentes Año 2 VPS + Nginx + PHP-FPM dedicado
Requiere cron sub-minute Para alertas en tiempo real VPS (Hostinger limita crones a cada 5min)
Necesita Redis / queue Cuando hay background jobs serios VPS con Redis instalado
Compliance (SOC2, ISO) Si Zonas Áridas crece a corporativo Cloud certificado (AWS, GCP, Azure)

Costo estimado por escenario

EscenarioHostingUSD/mesCuándo aplica
ActualHostinger Business shared5Hoy
Mid-tierHostinger VPS 2 GB12Año 1-2
ProfesionalHetzner CX31 (2 vCPU, 8 GB)11Año 2-3
Empresa pequeñaDigitalOcean Droplet 4 GB + Managed MySQL40Año 3+
HíbridoVPS app + DBaaS (PlanetScale / Neon)50-100Si se llega a > 100 users
Cloud completoAWS / GCP con RDS + ECS200+Sólo si compliance lo exige

Recomendación: empezar a planificar la migración a VPS cuando se cumpla cualquiera de:

  • BD > 3 GB
  • P95 > 4s y no se quieren implementar MVs
  • Se necesitan crones < 5 min (alertas tiempo real)
  • Se quiere Redis/Memcached

12.8 👥 Escalar el equipo

Etapas según tamaño del equipo

1-2 devs (hoy)

  • Stack simple (PHP + JS vanilla) bien justificado.
  • Documentación markdown extensa (~4.700 líneas).
  • Releases con semver real, changelog inline.
  • Manual: branches por feature, merge a main, deploy manual.

3-5 devs (próximos 12 meses)

  • Code review obligatorio (PRs en GitHub/GitLab).
  • Linter + formatter en pre-commit (PHP-CS-Fixer + Prettier).
  • CI básico: PHPUnit + PHPStan en cada PR.
  • Convención de commits (Conventional Commits).
  • Documentation as Code: este HTML versionado + ADRs.

5-10 devs (año 2)

  • Squads por dominio (venta, caja, cashflow, plataforma).
  • Tech Lead dedicado.
  • RFCs para cambios mayores.
  • OnCall rotation para incidentes.
  • SLA interno: uptime, P95, time-to-fix-bug.

12.9 🗺️ Roadmap consolidado de escalado

   AÑO 1 (Mayo 2026 → Mayo 2027)
   ──────────────────────────────
   Q3 2026  ►  Fase 0 Hardening (SEC-01/02/03) ★ Obligatorio
                Eliminar código muerto · Audit log
   Q3 2026  ►  Fase 1 Plataforma compartida
                _shared/, .env, fórmulas en clase
                CAJERO_PROV a tabla DB
   Q4 2026  ►  Fase 2.A · Vistas materializadas
                ventas_mensual_supervisor_mv y similares
                Cron diario de refresh
   Q4 2026  ►  Fase 2.B · Cron syncs automáticos
                Sync nocturno venta · cashflow alerts
   Q1 2027  ►  Fase 2.C · Cache HTTP + Lazy loading
                ETag en endpoints JSON
                Lazy load de tabs en dashboard

   AÑO 2 (Mayo 2027 → Mayo 2028)
   ──────────────────────────────
   Q2 2027  ►  Fase 3.A · Forecast cashflow + Alertas
   Q3 2027  ►  Fase 3.B · Anomaly detection + Recomendador límites
   Q3 2027  ►  Migración a VPS (Hetzner) + opcache.preload
   Q4 2027  ►  Fase 3.C · API pública + Power BI / Looker
   Q1 2028  ►  Particionado de ventas + Archivado >3 años
   Q1 2028  ►  Reporte ejecutivo PDF mensual automatizado

   AÑO 3 (Mayo 2028 → Mayo 2029)
   ──────────────────────────────
   Q2 2028  ►  Réplica de lectura · ProxySQL
   Q3 2028  ►  Multi-empresa: separar tenants
   Q4 2028  ►  Mobile app (PWA con offline-first)
   Q1 2029  ►  ML predictivo: stock óptimo, recomendador SKUs

KPIs del propio sistema a trackear

  • P95 de cada dashboard (objetivo: <1.5s año 1, <500ms año 2).
  • Uptime mensual (objetivo: 99.5% año 1, 99.9% año 2).
  • Cobertura de tests (objetivo: 40% año 1, 70% año 2).
  • Tiempo MTTR de incidentes (objetivo: <4h año 1, <1h año 2).
  • NPS interno (encuesta semestral a los usuarios).