13.1 📊 Estado visual actual
El sistema tiene una identidad visual consistente y bien lograda:
- ✅ Paleta dark/light unificada con clave
za-themeen localStorage. - ✅ Fuente Outfit (300-800) en todos los módulos.
- ✅ Header sticky con glass effect (backdrop-filter).
- ✅ Cards con sombras consistentes (
--shadow-sm/md/lg). - ✅ Border-radius consistente (8/14 px).
- ✅ Anti-flash de tema (script en primera línea del head).
- ✅ Iconografía emoji (sin dependencia de icon font).
Oportunidades de mejora
- ⚠️ Cada módulo redefine variables CSS — riesgo de divergencia.
- ⚠️ No hay design tokens formalizados (espaciado, radius, sombras).
- ⚠️ Componentes (cards, badges, callouts) repetidos sin abstracción.
- ⚠️ Sin librería de componentes documentada (style guide).
- ⚠️ Empty/loading/error states inconsistentes entre módulos.
13.2 🎨 Design system unificado
Tokens propuestos
/* /reportes/_shared/css/tokens.css */
:root {
/* SPACING (4px base) */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
/* RADIUS */
--radius-xs: 4px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 24px;
--radius-full: 9999px;
/* SHADOWS */
--shadow-xs: 0 1px 2px rgba(0,0,0,.05);
--shadow-sm: 0 2px 4px rgba(0,0,0,.04);
--shadow-md: 0 10px 25px -5px rgba(0,0,0,.08);
--shadow-lg: 0 20px 40px -12px rgba(0,0,0,.12);
--shadow-xl: 0 30px 60px -20px rgba(0,0,0,.15);
/* COLORS (semantic) */
--c-ok: #22c55e;
--c-warn: #f59e0b;
--c-err: #ef4444;
--c-info: #38bdf8;
--c-violet: #a855f7;
--c-accent: #3b82f6;
/* DURATION */
--d-fast: 150ms;
--d-base: 250ms;
--d-slow: 400ms;
/* EASING */
--e-out: cubic-bezier(.165, .84, .44, 1);
--e-bounce: cubic-bezier(.34, 1.56, .64, 1);
}
Componentes propuestos
/reportes/_shared/css/components/ ├── _buttons.css ← .btn, .btn-primary, .btn-secondary, sizes ├── _cards.css ← .card, .card-header, .card-body ├── _badges.css ← .badge-ok, .badge-warn, .badge-err ├── _kpis.css ← .kpi, .kpi.ok, .kpi.warn ├── _callouts.css ← .callout, .callout-info, etc ├── _tables.css ← reset + estilos base ├── _forms.css ← inputs, selects, switches ├── _modals.css ← overlay + content + close ├── _tooltips.css ← .tooltip con flecha CSS ├── _skeletons.css ← shimmer animations └── _theme.css ← dark/light tokens
Style guide interactivo
// /reportes/_shared/styleguide.html
// Una página con todos los componentes en uso, lado a lado
// Para que cualquier dev nuevo vea "así se ve un .btn-primary"
// Inspirado en Storybook (versión simple, sin build)
13.3 📭 Empty states consistentes
Hoy, cuando un período no tiene datos, se ve "0,00" o tablas vacías. Mejora:
componente <EmptyState> con ícono + mensaje + CTA.
Patrón propuesto
┌──────────────────────────────────────────────────────────────┐ │ │ │ 📭 │ │ │ │ Aún no hay ventas para este período │ │ │ │ Sincronizá las ventas desde ChessERP para ver │ │ KPIs, márgenes y rentabilidad. │ │ │ │ [⚡ Sincronizar ahora] │ │ │ └──────────────────────────────────────────────────────────────┘
HTML/CSS
<div class="empty-state">
<div class="empty-icon">📭</div>
<h3>Aún no hay ventas para este período</h3>
<p>Sincronizá las ventas desde ChessERP para ver KPIs, márgenes y rentabilidad.</p>
<a href="../sync_api.php?mes=<?=$mes?>&anio=<?=$anio?>" class="btn btn-primary">
⚡ Sincronizar ahora
</a>
</div>
.empty-state {
text-align: center;
padding: var(--space-12) var(--space-6);
color: var(--text-muted);
}
.empty-state .empty-icon {
font-size: 4rem;
margin-bottom: var(--space-4);
opacity: .6;
}
.empty-state h3 {
font-size: 1.15rem;
color: var(--text-strong);
margin-bottom: var(--space-2);
}
.empty-state p {
max-width: 40ch;
margin: 0 auto var(--space-6);
font-size: .92rem;
}
Variantes por contexto
| Contexto | Ícono | Mensaje sugerido |
|---|---|---|
| Sin períodos cargados | 📭 | "Aún no hay datos. Sincronizá tu primer mes para empezar." |
| Sin resultados en filtro | 🔍 | "No encontramos coincidencias. Probá con otros filtros." |
| Sin permisos | 🔒 | "No tenés permiso para ver este reporte. Hablalo con un admin." |
| Error de carga | ⚠️ | "No pudimos cargar los datos. [Reintentar]" |
| Catálogo vacío | 📦 | "El catálogo de artículos está vacío. Subí el Excel ahora." |
| Sin cheques en cartera | 🧾 | "No hay cheques registrados. Subí la cartera de cheques." |
13.4 ⏳ Loading states con skeletons
Hoy: spinners genéricos o nada. Mejora: skeletons que respetan el layout del componente final.
Patrón skeleton
.skeleton {
background: linear-gradient(
90deg,
var(--bg-card-alt) 0%,
var(--accent-soft) 50%,
var(--bg-card-alt) 100%
);
background-size: 200% 100%;
animation: shimmer 1.4s ease-in-out infinite;
border-radius: var(--radius-sm);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Variantes por uso */
.skeleton-text-sm { height: 12px; width: 60%; }
.skeleton-text-lg { height: 24px; width: 80%; }
.skeleton-kpi-value { height: 36px; width: 70%; margin: 8px 0; }
.skeleton-row { height: 40px; width: 100%; margin-bottom: 6px; }
.skeleton-circle { width: 40px; height: 40px; border-radius: 50%; }
Skeleton de KPI card
┌────────────────────────────┐ │ ░░░░░░░░░░░░░░░░░ │ ← label skeleton │ │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ ← value skeleton (shimmer) │ │ │ ░░░░░░░░░░░░░░░░░░░░░░░ │ ← delta skeleton └────────────────────────────┘
13.5 💬 Feedback visual (toasts y alerts)
Hoy: alert() nativos o errores silentes. Mejora:
sistema de toasts no bloqueantes.
Toast component
// /reportes/_shared/js/toast.js
const Toast = {
show(msg, type = 'info', duration = 4000) {
const el = document.createElement('div');
el.className = `toast toast-${type}`;
el.innerHTML = `<span>${this.icon(type)}</span> ${msg}`;
document.body.appendChild(el);
setTimeout(() => el.classList.add('show'), 10);
setTimeout(() => {
el.classList.remove('show');
setTimeout(() => el.remove(), 300);
}, duration);
},
ok(msg) { this.show(msg, 'ok'); },
warn(msg) { this.show(msg, 'warn'); },
err(msg) { this.show(msg, 'err', 6000); },
info(msg) { this.show(msg, 'info'); },
icon(t) {
return {ok:'✅', warn:'⚠️', err:'❌', info:'💡'}[t] || '';
}
};
// Uso:
Toast.ok('Sincronización completada: 22.456 filas insertadas');
Toast.err('Error de conexión con ChessERP. Reintentando…');
.toast {
position: fixed;
top: 1.5rem;
right: 1.5rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 4px solid;
border-radius: var(--radius-sm);
padding: 1rem 1.5rem;
box-shadow: var(--shadow-lg);
display: flex;
gap: .75rem;
align-items: center;
transform: translateX(120%);
transition: transform .3s var(--e-out);
z-index: 9999;
max-width: 420px;
}
.toast.show { transform: translateX(0); }
.toast-ok { border-left-color: var(--c-ok); }
.toast-warn { border-left-color: var(--c-warn); }
.toast-err { border-left-color: var(--c-err); }
.toast-info { border-left-color: var(--c-info); }
13.6 📈 Gráficos mejorados
Chart.js — tweaks para mejor lectura
// Config global para todos los charts
Chart.defaults.font.family = "'Outfit', sans-serif";
Chart.defaults.font.size = 12;
Chart.defaults.color = getCSS('--text-muted');
Chart.defaults.borderColor = getCSS('--border');
Chart.defaults.plugins.tooltip = {
backgroundColor: getCSS('--bg-card'),
titleColor: getCSS('--text-strong'),
bodyColor: getCSS('--text-main'),
borderColor: getCSS('--border'),
borderWidth: 1,
padding: 12,
cornerRadius: 8,
displayColors: true,
boxPadding: 4,
titleFont: { weight: 700, size: 13 },
bodyFont: { size: 12 },
// Formato de números argentinos
callbacks: {
label(ctx) {
const v = ctx.parsed.y;
const fmt = new Intl.NumberFormat('es-AR', {
style: 'currency', currency: 'ARS', maximumFractionDigits: 0
});
return `${ctx.dataset.label}: ${fmt.format(v)}`;
}
}
};
Anotaciones (chartjs-plugin-annotation)
Marcar eventos importantes en el chart de "Evolución 12 meses":
// Anotaciones útiles:
// · Línea horizontal: promedio histórico
// · Banda: rango ±1 stddev
// · Línea vertical: cambio de gerente / promo importante
// · Punto: anomalía detectada (z-score >2)
plugins: {
annotation: {
annotations: {
promedio: {
type: 'line',
scaleID: 'y',
value: ventasPromedio,
borderColor: getCSS('--c-info'),
borderDash: [6, 6],
label: { content: 'Promedio 12m', enabled: true, position: 'end' }
},
promo: {
type: 'line',
scaleID: 'x',
value: 'Ene 26',
borderColor: getCSS('--c-violet'),
label: { content: '🎁 Promo Año Nuevo', enabled: true }
}
}
}
}
Sparklines en KPI cards
Cada KPI numérico podría tener mini-chart (~80×30px) con tendencia 12 meses:
┌────────────────────────────┐ │ VENTAS NETAS │ │ │ │ $ 591.307.925 │ │ │ │ ╱╲ ╱╲ │ ← sparkline │ ╲ ╱ ╲ ╱ ╲ ╱ │ │ ╲╱ ╲╱ ╲ ╱ │ │ ╲╱ │ │ +12.3 % vs anterior │ └────────────────────────────┘
Heatmaps para patrones temporales
Útil para:
- Gastos por mes × rubro (cuándo se concentra cada gasto).
- Ventas por día de semana × mes (estacionalidad fina).
- Cheques recibidos por día × cliente (concentración).
13.7 📋 Informes mejorados
1 · Tarjetas KPI con tendencia y meta
┌───────────────────────────────────────────────────────┐ │ 💰 MARGEN GENERAL │ │ │ │ 20,34 % ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━░░░░ │ │ (barra de progreso vs meta 25%) │ │ │ │ ↑ +1,2 pp vs Mar 26 │ │ ⚠ -4,7 pp vs meta (25 %) │ │ │ │ ▁▂▃▅▆▆▅▄▃▄▅▆ (12 meses) │ └───────────────────────────────────────────────────────┘
2 · Drill-down universal con breadcrumbs
// Al hacer drill-down desde el dashboard:
// breadcrumb visual: "Resumen / Supervisor: LAR | A. SANCHEZ / Top proveedores"
// con flechas ← clicables para volver atrás
3 · Comparativa lado a lado
┌─────────────────────────────────────────────────────────────┐ │ Comparar: [Abril 2026 ▼] vs [Marzo 2026 ▼] │ ├─────────────────────────────────────────────────────────────┤ │ │ ABR 2026 │ MAR 2026 │ Δ │ │ Vta │ $591.3 M ━━━│ $564.5 M ━━ │+4.7%│ │ Margen │ 20.34 % │ 19.12 % │+1.2 │ │ Comprobantes │ 22.546 │ 21.864 │+3.1%│ │ Clientes únicos │ 1.234 │ 1.198 │+3.0%│ │ Bultos │ 14.230 │ 13.890 │+2.4%│ │ Ticket promedio │ $26.224 │ $25.821 │+1.6%│ └─────────────────────────────────────────────────────────────┘
4 · Mensajes en lenguaje natural
// En vez de tabla fría, generar "insights" automáticos:
📊 Resumen del mes:
Abril 2026 vendió $591.3M (+4.7% vs Marzo).
El margen mejoró a 20.34% (+1.2pp) gracias a:
· Reducción de gastos logísticos en SGO (-12%)
· Recuperación del proveedor PURINA (+18% margen)
⚠️ Atención:
El supervisor LAR | A. SANCHEZ tuvo el peor desempeño:
ventas -8% vs su promedio. Sugerencia: revisar zona.
✅ Mejores resultados:
Cliente nuevo "Veterinaria Del Sur" generó $1.2M con margen 28%.
Es candidato a aumentar línea de crédito (sugerencia: $2M).
5 · Filtros guardables como "vistas"
┌─ Mis Vistas ─────────────────────────────────────────┐ │ ★ Cierre mensual ZA (actual sync + ZA) │ │ ★ Top deudores +60d (snapshot reciente) │ │ ★ Combustible 2026 (rubro filtrado año) │ │ ★ Performance cobranza JM (vendedor) │ │ │ │ [+ Guardar vista actual] │ └──────────────────────────────────────────────────────┘
13.8 ♿ Accesibilidad
Quick wins
- ARIA labels en todos los botones-ícono (theme toggle, fullscreen, close).
- Contraste WCAG AA: verificar con axe-core. La paleta dark actual cumple en general.
- Focus visible: outline azul claro en cada elemento focuseable.
- Skip link: "Saltar al contenido principal" para teclado.
- Keyboard navigation: Tab debe llegar a cada control.
- Semántica: usar
<nav>,<main>,<aside>,<article>en vez de divs.
Tablas accesibles
<table>
<caption>Top 10 proveedores por ventas</caption>
<thead>
<tr>
<th scope="col">Proveedor</th>
<th scope="col">Ventas</th>
<th scope="col">Margen</th>
</tr>
</thead>
...
</table>
Reducir motion para usuarios sensibles
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
13.9 ✨ Microinteracciones
- Hover en filas Tabulator: ya existe, mejorar con leve elevation.
- Click feedback: ripple sutil al clicar botones (200ms).
- Tooltip en KPIs: "Click para ver detalle por entidad".
- Animar números:
countUp.jspara que KPIs cuenten al cargar. - Skeleton → real: fade-in suave (300ms) cuando llega el dato.
- Tab switching: ya tiene transición; agregar slide horizontal sutil.
- Drag para reordenar: cards de KPI reordenables y persistir orden.
- Modo presentación: F11 maximiza dashboard, oculta sidebar (para reuniones).
13.10 🖨️ Print-friendly
Para imprimir reportes en papel o exportar como PDF nativo del browser:
@media print {
/* Quitar elementos no esenciales */
.site-header, .sidebar, .tab-buttons,
.filter-card, .back-top, button { display: none !important; }
/* Expandir todo */
.main { grid-template-columns: 1fr !important; padding: 0 !important; }
.content { max-width: 100% !important; }
/* Páginas A4 */
@page {
size: A4;
margin: 1.5cm;
}
/* Forzar page break antes de cada sección grande */
.section { page-break-inside: avoid; }
h2 { page-break-after: avoid; }
/* Color print */
body { background: white !important; color: black !important; }
.kpi, .card { box-shadow: none; border: 1px solid #ccc; }
pre { border: 1px solid #ccc; page-break-inside: avoid; }
/* Mostrar URL al lado de links */
a[href^="http"]:after { content: " (" attr(href) ")"; font-size: .8em; }
}
Botón "Imprimir reporte"
En cada dashboard, botón visible que ejecuta window.print() + sugerencia
"Guardar como PDF" en el diálogo.