F0 🔒 Fase 0 · Hardening (1–2 semanas)

⚠️ Bloqueante para cualquier crecimiento
Esta fase es obligatoria antes de tocar features. Costo: ~10 días de desarrollo.

F0-1 · Implementar autenticación en endpoints administrativos

Crítico · 2 días

Reactivar session_start() + verificación de auth en:

  • caja/reset_db.php · sync.php · sync_bulk.php
  • cashflow/reset_db.php · sync.php · api/save.php · api/init.php · api/delete-sync.php
  • venta/sync_api.php · sync_api_process.php · reinit_import.php
  • articulos/sync.php · api/init.php · api/save.php
  • caja/api/save.php · save_raw.php · init.php

Diseño propuesto

// /reportes/_shared/auth.php
function require_login(): array {
    session_start();
    if (!isset($_SESSION['usuario_id'])) {
        if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
            http_response_code(401);
            exit(json_encode(['error' => 'No autenticado']));
        }
        header('Location: /login.php?next=' . urlencode($_SERVER['REQUEST_URI']));
        exit;
    }
    return [
        'id'    => $_SESSION['usuario_id'],
        'email' => $_SESSION['usuario_email'],
        'roles' => $_SESSION['usuario_roles'] ?? [],
    ];
}

function require_role(array $rolesPermitidos): array {
    $user = require_login();
    if (empty(array_intersect($user['roles'], $rolesPermitidos))) {
        http_response_code(403);
        exit(json_encode(['error' => 'No autorizado']));
    }
    return $user;
}

// En cada endpoint:
require_once __DIR__ . '/../_shared/auth.php';
$user = require_role(['admin', 'finanzas']);

F0-2 · Mover credenciales a .env

Crítico · 1 día
# /home/u120688891/.env  (FUERA del doc root)
DB_HOST=localhost
DB_NAME=u120688891_chess
DB_USER=u120688891_chess
DB_PASS=t2#h*wvQ2./wZaS
CHESS_API_URL=https://zonasaridas.chesserp.com/AR1185/web/api/chess/v1
CHESS_API_USER=api_zonas
CHESS_API_PASS=z0n4saridas
APP_ENV=production
APP_DEBUG=false
// /reportes/_shared/config.php
$envPath = '/home/u120688891/.env';
if (!file_exists($envPath)) die('Config not found');

foreach (parse_ini_file($envPath) as $k => $v) {
    if (!defined($k)) define($k, $v);
}

// Aliases para retrocompatibilidad
if (!defined('REPORTES_DB_HOST')) define('REPORTES_DB_HOST', DB_HOST);
// ... etc

// SITE_VERSION local del módulo
require_once __DIR__ . '/../venta/config/site_version.php';

Los 5 config/database.php se reemplazan por:

<?php
require_once __DIR__ . '/../../_shared/config.php';
// Constantes propias del módulo (no credenciales)
define('UPLOAD_MAX_MB', 50);

F0-3 · Bloquear archivos sensibles

Crítico · 30 min
# /reportes/.htaccess
<FilesMatch "\.(sql|md|bak|env|log|swp|orig|jsx|json5)$">
    Order allow,deny
    Deny from all
</FilesMatch>

# Bloquear lista de archivos específicos
<Files "u120688891_chess.sql">
    Order allow,deny
    Deny from all
</Files>

<Files "_debug_samarelli.php">
    Order allow,deny
    Deny from all
</Files>

# Bloquear directorios de config
<DirectoryMatch "/config/">
    Order allow,deny
    Deny from all
</DirectoryMatch>

# Disable directory listing
Options -Indexes

Adicionalmente, mover el dump fuera del doc root:

mv /home/u120688891/public_html/reportes/u120688891_chess.sql \
   /home/u120688891/backups/u120688891_chess_2026-05-21.sql

F0-4 · Reemplazar interpolaciones SQL

Alto · 2 días

Auditoría completa con grep:

grep -rn 'query("[^"]*\$' /reportes --include="*.php"
# Lista todas las queries con interpolación de variables

Reemplazar todas por prepared statements. Para nombres de tabla (no parametrizables), usar whitelist explícita.


F0-5 · Eliminar código muerto

Alto · 1 hora
  • Borrar caja/dashboard.jsx (22 KB legado React).
  • Borrar venta/_debug_samarelli.php (debug ad-hoc).
  • Confirmar uso de listas/api/sync_process.php antes de borrarlo.

F0-6 · Crear robots.txt

Medio · 5 min
# /reportes/robots.txt
User-agent: *
Disallow: /
# Disallow: /reportes/  (si robots.txt está en raíz del sitio)

# Para evitar indexación accidental por buscadores

F0-7 · Audit log de accesos

Medio · 1 día
CREATE TABLE reportes_audit_log (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  usuario_id  INT,
  email       VARCHAR(150),
  ruta        VARCHAR(255),
  metodo      VARCHAR(10),
  ip          VARCHAR(45),
  user_agent  VARCHAR(255),
  payload     JSON,                       -- input recibido (sin passwords!)
  response_code INT,
  duracion_ms INT,
  creado_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_usuario (usuario_id),
  INDEX idx_fecha   (creado_at),
  INDEX idx_ruta    (ruta)
);

-- Middleware en _shared/audit.php que loggea cada hit
-- Especialmente útil para endpoints destructivos

📊 Resumen de Fase 0

Tareas críticas
3
Tareas altas
2
Tareas medias
2
Duración
~10 d

F1 🏗️ Fase 1 · Plataforma compartida (3–4 semanas)

Reducir duplicación, preparar la base para escalar.

F1-1 · Crear paquete _shared/

/reportes/
├── _shared/
│   ├── config.php           ← carga .env + constantes globales
│   ├── auth.php             ← require_login(), require_role()
│   ├── db.php               ← PDO singleton + bootstrap automático
│   ├── logger.php           ← log_error(), log_event(), log_audit()
│   ├── rate_limit.php       ← rate_limit($key, $max, $window)
│   ├── formatters.php       ← fmtMoney, fmtPct, fmtTons, fmtNum (PHP)
│   ├── response.php         ← jsonOk(), jsonErr(), api_response()
│   ├── validators.php       ← validateInt, validateEnum, validateDate
│   ├── cache.php            ← cache simple file-based para snapshots
│   └── version.php          ← REPORTES_PLATFORM_VERSION
└── ...

F1-2 · Estandarizar respuestas de API

// /reportes/_shared/response.php
function api_response($data = null, ?string $error = null, int $code = 200): void {
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    header('X-Content-Type-Options: nosniff');
    echo json_encode([
        'ok'        => $error === null,
        'data'      => $data,
        'error'     => $error,
        'timestamp' => date('c'),
        'version'   => REPORTES_PLATFORM_VERSION,
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

// Uso:
api_response(['ventas' => $rows]);
api_response(null, 'Sync not found', 404);

F1-3 · Migrar CAJERO_PROV a tabla DB

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
);

-- Seed inicial desde el JS hardcoded actual
INSERT INTO caja_cajero_provincia (cajero, provincia) VALUES
  ('lar-cajero1', 'LA RIOJA'),
  ('sgo-cajero2', 'SANTIAGO DEL ESTERO'),
  ('cat-cajero3', 'CATAMARCA'),
  ...;

-- UI: /reportes/caja/admin/cajeros.php (CRUD básico)
-- Pipeline JS hace fetch /caja/api/cajero_provincia.php al iniciar

F1-4 · Extraer fórmulas a clase reutilizable

// /reportes/venta/includes/VentaFormulas.php
final class VentaFormulas {
    public const CBTE_KEY = "CONCAT(idEmpresa,'-',idDocumento,'-',letra,'-',IFNULL(serie,0),'-',IFNULL(nrodoc,0))";

    public const SUM_VENTAS_REALES = "SUM(CASE WHEN dsArticulo IS NOT NULL AND dsArticulo != '' THEN subtotalNeto ELSE 0 END)";
    public const SUM_COSTO_NETO    = "SUM(preciocomprant * cantidadesTotal)";

    public static function exprContribucion(): string {
        return '(' . self::SUM_VENTAS_REALES . ' - ' . self::SUM_COSTO_NETO . ')';
    }

    public static function exprMargenPct(): string {
        return "CASE WHEN " . self::SUM_VENTAS_REALES . " > 0 "
             . "THEN " . self::exprContribucion() . " / " . self::SUM_VENTAS_REALES . " "
             . "ELSE 0 END";
    }
}

// Uso en data_api.php y filter.php:
use VentaFormulas as F;
$sql = "SELECT " . F::exprContribucion() . " AS contribucion, ...";

F1-5 · Composer autoload

Si crece la cantidad de helpers, vale la pena agregar Composer:

// composer.json
{
    "require": {
        "php": ">=8.0",
        "vlucas/phpdotenv": "^5.5",
        "monolog/monolog": "^3.0"
    },
    "autoload": {
        "psr-4": {
            "ZA\\Reportes\\": "_shared/"
        }
    }
}

F2 📊 Fase 2 · Capa BI estable (4–6 semanas)

Convierte el sistema en una BI propiamente dicha.

F2-1 · Vistas materializadas

CREATE TABLE ventas_mensual_supervisor_mv (
  sync_id          INT,
  mes              TINYINT,
  anio             SMALLINT,
  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)
);

-- Refresh cron diario 04:00:
TRUNCATE ventas_mensual_supervisor_mv;
INSERT INTO ventas_mensual_supervisor_mv
SELECT sync_id, ... FROM ventas LEFT JOIN articulos_raw ... GROUP BY sync_id, dsSupervisor;

Reduce las queries del dashboard de 800-2000 ms a <50 ms.

F2-2 · Cron jobs

# Crontab en Hostinger
# Sync diario del mes en curso (ventas)
0 3 * * *  php /home/u120688891/public_html/reportes/venta/cron/sync_mes_actual.php >> /var/log/za/venta_sync.log 2>&1

# Refresh de MVs
30 3 * * * php /home/u120688891/public_html/reportes/cron/refresh_mvs.php >> /var/log/za/mvs.log 2>&1

# Email diario de cashflow
0 7 * * 1-5 php /home/u120688891/public_html/reportes/cashflow/cron/email_diario.php

# Cleanup de cache cada hora
0 * * * * find /home/u120688891/public_html/reportes/_shared/cache -mmin +60 -delete

# Healthcheck cada 5 min
*/5 * * * * curl -s -o /dev/null -w "%{http_code}" https://zonasaridas.com.ar/reportes/health.php || \
            echo "[$(date)] healthcheck failed" >> /var/log/za/healthcheck.log

F2-3 · Cache HTTP en endpoints JSON

// /reportes/_shared/cache.php
function http_cache(string $key, callable $generator, int $ttl = 300): void {
    $etag = md5($key);
    $ifNoneMatch = trim($_SERVER['HTTP_IF_NONE_MATCH'] ?? '', '"');
    if ($ifNoneMatch === $etag) {
        http_response_code(304);
        exit;
    }
    header("ETag: \"$etag\"");
    header("Cache-Control: private, max-age=$ttl, must-revalidate");
    echo $generator();
}

// Uso en cashflow/api/data.php
http_cache(
    "cashflow:$corte:$sucursal:" . filemtime(__FILE__),
    fn() => json_encode($result)
);

F2-4 · Export Excel/CSV de cualquier dashboard

// /reportes/_shared/export.php
function export_csv(array $rows, string $filename): void {
    header('Content-Type: text/csv; charset=utf-8');
    header("Content-Disposition: attachment; filename=\"$filename\"");
    echo "\xEF\xBB\xBF"; // BOM UTF-8 para Excel
    $out = fopen('php://output', 'w');
    if ($rows) fputcsv($out, array_keys($rows[0]));
    foreach ($rows as $r) fputcsv($out, $r);
    fclose($out);
}

// Botón "📥 Exportar" en cada Tabulator
// Tabulator soporta nativamente:
table.download("csv", "ventas_abr2026.csv");
table.download("xlsx", "ventas_abr2026.xlsx", {sheetName: "Ventas"});

F2-5 · Dashboard multi-período comparativo

Permitir seleccionar 2 períodos lado a lado para comparar (MoM, YoY):

┌────────────────────────────────────────────────────────────┐
│  📊 Comparativo: [Mar 2026 ▼] vs [Mar 2025 ▼]              │
├────────────────────────────────────────────────────────────┤
│                  │   MAR 2026   │   MAR 2025   │   YoY     │
│ Vta              │ $ 564.5 M    │ $ 421.3 M    │  +33.9%   │
│ Margen           │   19.8 %     │   21.4 %     │  -1.6 pp  │
│ Comprobantes     │   21.864     │   18.349     │  +19.2%   │
│ Ticket promedio  │ $ 25.821     │ $ 22.961     │  +12.5%   │
└────────────────────────────────────────────────────────────┘

F2-6 · Drill-down universal

Hacer que CUALQUIER KPI o celda sea clicable y abra detalle:

  • Click en "Vta total" → tabla de comprobantes del período.
  • Click en "Bultos" → distribución por SKU.
  • Click en una fila → mini-dashboard de esa entidad.

F3 🤖 Fase 3 · Inteligencia y proyecciones (6–8 semanas)

F3-1 · Forecast de cashflow con regresión

Sustituir la "estimación por cuartos" del calendario semanal por modelo basado en histórico:

// Pseudocódigo
function predict_cobranza_semanal($cliente_id, $semana_offset) {
    // 1. Obtener cobranzas históricas del cliente (últimos 24 meses)
    // 2. Para cada saldo en aging:
    //    ¿Qué % se cobró en semana N? (promedio histórico)
    // 3. Ajustar por:
    //    - Estacionalidad (mes calendario)
    //    - Vendedor (algunos cobran más rápido)
    //    - Tendencia (mejora o empeora últimos 3 meses)
    // 4. Devolver: { punto, intervalo_inferior, intervalo_superior }
}

F3-2 · Alertas activas

CREATE TABLE reportes_alertas (
  id           INT PRIMARY KEY AUTO_INCREMENT,
  tipo         VARCHAR(40),
  nivel        ENUM('info', 'warning', 'critical'),
  titulo       VARCHAR(255),
  mensaje      TEXT,
  payload      JSON,
  canal        SET('email', 'whatsapp', 'slack', 'dashboard'),
  destinatarios JSON,
  enviado_at   TIMESTAMP NULL,
  reconocido_at TIMESTAMP NULL,
  creado_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Cron diario evalúa reglas y crea alertas
-- Otro cron envía las pendientes

Reglas iniciales

  • Saldo proyectado a 30d < $X → email a finanzas.
  • Margen del mes < promedio histórico - 3% → email a gerencia.
  • Cliente con NDCON nuevo → WhatsApp al vendedor.
  • Cheque a presentar mañana → email a tesorería.
  • Sync que no se ejecuta hace 3 días → email a admin.

F3-3 · Detección de anomalías

// Z-score sobre gasto mensual por rubro × provincia
SELECT
  rubro, provincia, mes, anio,
  SUM(monto) AS gasto_actual,
  AVG(SUM(monto)) OVER (PARTITION BY rubro, provincia) AS media_historica,
  STDDEV(SUM(monto)) OVER (PARTITION BY rubro, provincia) AS stddev,
  (SUM(monto) - AVG(SUM(monto)) OVER (PARTITION BY rubro, provincia))
    / NULLIF(STDDEV(SUM(monto)) OVER (PARTITION BY rubro, provincia), 0) AS z_score
FROM caja_gastos
GROUP BY rubro, provincia, mes, anio
HAVING ABS(z_score) > 2;
-- |z| > 2 → anomalía estadística significativa

F3-4 · Recomendador de límite de crédito

Para cada cliente, sugerir un límite de crédito basado en:

  • Venta promedio mensual últimos 12 meses.
  • Plazo de pago dominante.
  • Historial de NDCONs (cheques rechazados).
  • Estacionalidad del cliente.
  • Variabilidad mes a mes.
limite_sugerido = venta_mensual_promedio
                  × (1 + plazo_dias / 30)
                  × factor_riesgo(antig, ndcons, dias_sin_comprar)

// factor_riesgo:
//   sano       → 1.5
//   normal     → 1.0
//   creciente  → 0.7
//   fantasma   → 0.3
//   incumplim. → 0.0  (cortar)

F3-5 · Reporte ejecutivo PDF mensual

Job mensual genera PDF de 8-12 páginas con:

  • Carátula con logo + mes + KPIs principales.
  • Resumen ejecutivo (1 página, 5-7 takeaways).
  • Detalle por área: Venta · Caja · Cashflow.
  • Comparativa MoM y YoY.
  • Anexo con tablas detalladas.

Stack sugerido: dompdf o mPDF (PHP-native, sin browser headless).

F3-6 · API pública (read-only)

// /reportes/api/v1/
//   GET /ventas/kpis?sync=28&sucursal=ZA
//   GET /caja/gastos?anio=2026&mes=4
//   GET /cashflow/snapshot?corte=2026-05-20

// Auth: API key (Bearer token)
// Rate limit: 1000 req/h por key
// Documentación: OpenAPI 3.0
// Permite integración con Power BI, Looker, Tableau

CREATE TABLE api_keys (
  id          INT PRIMARY KEY AUTO_INCREMENT,
  key_hash    VARCHAR(64) NOT NULL UNIQUE,
  nombre      VARCHAR(100),
  scopes      SET('venta:read','caja:read','cashflow:read','articulos:read'),
  rate_limit  INT DEFAULT 1000,
  ultima_uso  TIMESTAMP NULL,
  creado_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  expira_at   TIMESTAMP NULL,
  activo      TINYINT(1) DEFAULT 1
);

11.5 📋 Mejoras de informes existentes

Mejora #1 · Filtros guardables ("Mis vistas")

Cada usuario puede guardar combinaciones de filtros frecuentes:

  • "Cierre mensual ZA" = sync más reciente + sucursal ZONAS ARIDAS + sin filtro extra.
  • "Gastos LA RIOJA Q1" = provincia + rango mes/año.
  • "Top deudores +60d" = corte actual + tramo +60.

Mejora #2 · Exportación masiva

  • "Descargar TODO el dashboard como Excel" (cada sección = una hoja).
  • "Generar reporte PDF de este período" (1 click → PDF estilizado).
  • "Compartir link" → URL con todos los filtros embebidos.

Mejora #3 · Anotaciones por celda

Permitir agregar notas a cualquier KPI/fila:

CREATE TABLE reportes_anotaciones (
  id          INT PRIMARY KEY AUTO_INCREMENT,
  contexto    VARCHAR(100),       -- 'venta:sync:28:supervisor:LAR_ANDREA'
  usuario_id  INT,
  titulo      VARCHAR(255),
  texto       TEXT,
  visible_para SET('yo','equipo','todos'),
  creado_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_contexto (contexto)
);

Útil para "Esta caída de margen fue por la promo de Easter" o "Cheque rechazado, ya conversado con el cliente".

Mejora #4 · Comparativa contra presupuesto

CREATE TABLE presupuestos (
  id          INT PRIMARY KEY AUTO_INCREMENT,
  anio        SMALLINT,
  mes         TINYINT,
  dimension   VARCHAR(50),         -- 'venta_total', 'gasto_combustible', 'margen_pct'
  scope       VARCHAR(100),        -- 'ZA', 'LA_RIOJA', etc
  valor       DECIMAL(15,2),
  creado_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_periodo_scope (anio, mes, dimension, scope)
);

-- En el dashboard: "Vta: $591M (vs $580M presup) +1.9% ✓"

11.6 🔭 Observabilidad

Métricas a trackear

  • Performance: tiempo de respuesta por endpoint (p50, p95, p99).
  • Errores: tasa por hora, tipo (PHP fatal, DB error, API timeout).
  • Sync health: cuántos minutos pasaron desde último sync exitoso.
  • BD: tamaño de cada tabla, slow queries (>500ms).
  • Negocio: # users diarios, dashboards más visitados.

Stack sugerido (low cost)

  • Logs: archivos JSONL en /var/log/za/ rotados por mes.
  • Métricas: tabla reportes_metrics + cron de agregación.
  • Dashboard interno: /reportes/admin/observability.php.
  • Alerting: cron que evalúa thresholds y envía email/WhatsApp.
CREATE TABLE reportes_metrics (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  metric      VARCHAR(80),
  value       DECIMAL(15,4),
  tags        JSON,
  recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_metric_time (metric, recorded_at)
) ENGINE=InnoDB;

-- Particionar por mes para retención
ALTER TABLE reportes_metrics PARTITION BY RANGE (TO_DAYS(recorded_at)) (
  PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')),
  PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')),
  ...
);

11.7 🧪 Testing

Estado actual

El proyecto NO tiene tests automatizados. Verifica todo manualmente con "casos conocidos" (SAMARELLI, MOLINA, etc.).

Propuesta mínima viable

/reportes/tests/
├── PHPUnit.xml
├── bootstrap.php           ← carga _shared/config + DB de test
│
├── Unit/
│   ├── FormulasTest.php    ← VentaFormulas::exprMargenPct() es válido SQL
│   ├── FormattersTest.php  ← formatMoney(1234567.89) === '$ 1.234.568'
│   ├── ParserTest.php      ← parseMovDetalleMap regresión bug v2.0.2
│
├── Integration/
│   ├── ApiKpisTest.php     ← _apiKpis devuelve estructura esperada
│   ├── PipelineTest.php    ← pipeline 3-tier sobre dataset fixture
│   ├── ChessApiTest.php    ← (mock) login + fetchVentas con sample
│
└── Regression/
    ├── SamarelliTest.php   ← margen 25.20% en período Abr 2026
    ├── MolinaTest.php      ← margen 21.09% con NDCONs
    ├── TotalesCoherentes.php ← 6 tabs Rentabilidad dan mismo TOTAL

Stack: PHPUnit 10+ con DB en SQLite o MariaDB de test.

Run en GitHub Actions o GitLab CI antes de cada deploy.

11.8 🚀 CI/CD

Pipeline propuesto

   git push origin main
        │
        ▼
   ┌─────────────────────────────────────────────────────────────┐
   │ GitHub Actions / GitLab CI                                  │
   │                                                             │
   │ 1. PHP Lint                                                 │
   │    php -l en cada .php tocado                               │
   │                                                             │
   │ 2. PHPStan / Psalm                                          │
   │    Type checking nivel 5                                    │
   │                                                             │
   │ 3. PHPUnit                                                  │
   │    Unit + Integration + Regression                          │
   │                                                             │
   │ 4. Security Audit                                           │
   │    composer audit + grep sensitive patterns                 │
   │                                                             │
   │ 5. Build assets (si hay)                                    │
   │                                                             │
   │ 6. Deploy a staging (auto)                                  │
   │    rsync a /reportes-staging/                               │
   │                                                             │
   │ 7. Smoke tests en staging                                   │
   │    curl /health.php → status 200                            │
   │                                                             │
   │ 8. Deploy a producción (manual approval)                    │
   │    rsync a /reportes/                                       │
   │    + invalidación de cache                                  │
   └─────────────────────────────────────────────────────────────┘

Versionado del análisis

Este documento de análisis sigue su propio semver. Ver §14 Changelog.

11.9 👥 Equipo y procesos

Roles recomendados

RolResponsabilidadesDedicación
Tech Lead Arquitectura, code reviews, decisiones de stack, mentoría 50% del proyecto
Backend Dev (PHP) Endpoints, syncs, BD, refactor 100%
Frontend Dev (JS) Dashboards, Tabulator, Chart.js, UX 50-100%
Data Analyst Validar fórmulas, nuevos reportes, training a usuarios 25-50%
Product Owner (negocio) Priorizar features, validar resultados, vínculo con stakeholders 25%

Procesos sugeridos

  • Sprints de 2 semanas con demo al final.
  • Code review obligatorio antes de merge a main.
  • Standup async diario (mensaje en Slack/WhatsApp).
  • Retro mensual + revisión del roadmap.
  • Backup verificado semanal (no solo automatizar — VERIFICAR que restaure).
  • Verificaciones del cliente al final de cada feature (caso real como SAMARELLI/MOLINA → no cambia el resultado).

Documentación viva

Mantener el sistema de docs markdown existente (~4.700 líneas) + este análisis HTML versionado. Bumpear las versiones con cada cambio mayor.