Skip to content

Instantly share code, notes, and snippets.

@skozz
Created March 9, 2026 14:45
Show Gist options
  • Select an option

  • Save skozz/4f049fbe3c5b0e1069b485d5ddda33fa to your computer and use it in GitHub Desktop.

Select an option

Save skozz/4f049fbe3c5b0e1069b485d5ddda33fa to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flujo de Fichaje y Firma — Compliance España</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0b0f;
--surface: #12131a;
--surface-2: #1a1b25;
--border: #2a2b3a;
--text: #e8e9f0;
--text-dim: #8889a0;
--accent: #6366f1;
--accent-glow: rgba(99, 102, 241, 0.15);
--green: #22c55e;
--green-glow: rgba(34, 197, 94, 0.12);
--amber: #f59e0b;
--amber-glow: rgba(245, 158, 11, 0.12);
--red: #ef4444;
--red-glow: rgba(239, 68, 68, 0.12);
--cyan: #06b6d4;
--cyan-glow: rgba(6, 182, 212, 0.12);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'DM Sans', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
}
.noise {
position: fixed;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 0;
}
.container {
position: relative;
z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 40px 24px 60px;
}
header {
text-align: center;
margin-bottom: 48px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--accent-glow);
border: 1px solid rgba(99, 102, 241, 0.25);
border-radius: 100px;
padding: 6px 16px;
font-size: 12px;
font-weight: 600;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 16px;
}
.badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
h1 {
font-size: clamp(28px, 4vw, 42px);
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.02em;
margin-bottom: 12px;
}
h1 span { color: var(--accent); }
.subtitle {
color: var(--text-dim);
font-size: 15px;
line-height: 1.6;
max-width: 600px;
margin: 0 auto;
}
/* Tab Navigation */
.tabs {
display: flex;
gap: 4px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 4px;
margin-bottom: 36px;
overflow-x: auto;
}
.tab {
flex: 1;
min-width: 120px;
padding: 10px 16px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--text-dim);
font-family: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.tab:hover { color: var(--text); background: var(--surface-2); }
.tab.active { color: var(--text); background: var(--accent); }
/* Flow Section */
.flow-section {
display: none;
animation: fadeIn 0.3s ease;
}
.flow-section.active { display: block; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* Flow Steps */
.flow {
position: relative;
display: flex;
flex-direction: column;
gap: 0;
}
.flow::before {
content: '';
position: absolute;
left: 23px;
top: 36px;
bottom: 36px;
width: 2px;
background: linear-gradient(to bottom, var(--accent) 0%, var(--border) 100%);
opacity: 0.4;
}
.step {
position: relative;
display: flex;
gap: 20px;
padding: 16px 0;
}
.step-marker {
position: relative;
z-index: 2;
width: 48px;
height: 48px;
min-width: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
border: 2px solid;
}
.step-marker.action {
background: var(--accent-glow);
border-color: rgba(99, 102, 241, 0.35);
color: var(--accent);
}
.step-marker.check {
background: var(--amber-glow);
border-color: rgba(245, 158, 11, 0.35);
color: var(--amber);
}
.step-marker.success {
background: var(--green-glow);
border-color: rgba(34, 197, 94, 0.35);
color: var(--green);
}
.step-marker.data {
background: var(--cyan-glow);
border-color: rgba(6, 182, 212, 0.35);
color: var(--cyan);
}
.step-marker.alert {
background: var(--red-glow);
border-color: rgba(239, 68, 68, 0.35);
color: var(--red);
}
.step-content {
flex: 1;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px 24px;
transition: border-color 0.2s;
}
.step-content:hover {
border-color: rgba(99, 102, 241, 0.3);
}
.step-title {
font-size: 15px;
font-weight: 700;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 8px;
}
.step-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 2px 8px;
border-radius: 4px;
background: var(--surface-2);
color: var(--text-dim);
}
.step-desc {
font-size: 13.5px;
color: var(--text-dim);
line-height: 1.65;
}
.step-desc strong { color: var(--text); font-weight: 600; }
.data-fields {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
.field {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
padding: 4px 10px;
border-radius: 6px;
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text-dim);
}
.field .dot {
width: 5px;
height: 5px;
border-radius: 50%;
}
.field .dot.required { background: var(--red); }
.field .dot.recommended { background: var(--amber); }
.field .dot.optional { background: var(--text-dim); }
/* Legal References */
.legal-ref {
margin-top: 40px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 28px;
}
.legal-ref h3 {
font-size: 16px;
font-weight: 700;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.legal-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
}
.legal-card {
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px;
}
.legal-card .tag {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
margin-bottom: 6px;
}
.legal-card .title {
font-size: 13px;
font-weight: 600;
margin-bottom: 4px;
}
.legal-card .desc {
font-size: 12px;
color: var(--text-dim);
line-height: 1.55;
}
/* Sanctions bar */
.sanctions {
margin-top: 24px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.sanction {
text-align: center;
padding: 16px;
border-radius: 10px;
border: 1px solid var(--border);
}
.sanction.leve { background: var(--amber-glow); border-color: rgba(245,158,11,0.25); }
.sanction.grave { background: rgba(249,115,22,0.1); border-color: rgba(249,115,22,0.25); }
.sanction.muy-grave { background: var(--red-glow); border-color: rgba(239,68,68,0.25); }
.sanction .level {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 4px;
}
.sanction.leve .level { color: var(--amber); }
.sanction.grave .level { color: #f97316; }
.sanction.muy-grave .level { color: var(--red); }
.sanction .amount {
font-size: 18px;
font-weight: 700;
}
.sanction .per {
font-size: 11px;
color: var(--text-dim);
margin-top: 2px;
}
/* Legend */
.legend {
display: flex;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 20px;
padding: 12px 16px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
font-size: 12px;
color: var(--text-dim);
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
/* Arrow connector */
.arrow-down {
display: flex;
justify-content: center;
padding: 4px 0;
color: var(--text-dim);
opacity: 0.4;
font-size: 12px;
}
/* Qamarero Status Panel */
.qamarero-status {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
margin-bottom: 8px;
}
.qs-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 20px;
}
.qs-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 700;
}
.qs-logo {
width: 32px;
height: 32px;
border-radius: 8px;
background: linear-gradient(135deg, var(--accent), #a855f7);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 800;
color: #fff;
}
.qs-summary {
font-size: 13px;
color: var(--text-dim);
}
.qs-pending {
color: var(--amber);
font-weight: 600;
}
.qs-grid {
display: flex;
flex-direction: column;
gap: 8px;
}
.qs-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--surface-2);
}
.qs-item.covered {
border-color: rgba(34, 197, 94, 0.2);
}
.qs-item.partial {
border-color: rgba(245, 158, 11, 0.25);
background: rgba(245, 158, 11, 0.04);
}
.qs-item.pending {
border-color: rgba(245, 158, 11, 0.35);
background: rgba(245, 158, 11, 0.06);
}
.qs-item.blocker {
border-color: rgba(239, 68, 68, 0.35);
background: rgba(239, 68, 68, 0.06);
}
.qs-icon {
font-size: 16px;
min-width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.qs-item.covered .qs-icon { color: var(--green); }
.qs-item.partial .qs-icon { color: var(--amber); }
.qs-item.pending .qs-icon { color: var(--amber); }
.qs-item.blocker .qs-icon { color: var(--red); }
.qs-body { flex: 1; }
.qs-item-title {
font-size: 13.5px;
font-weight: 600;
margin-bottom: 2px;
}
.qs-item-desc {
font-size: 12.5px;
color: var(--text-dim);
line-height: 1.5;
}
.qs-badge {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 4px 10px;
border-radius: 6px;
white-space: nowrap;
flex-shrink: 0;
}
.qs-badge.done {
background: rgba(34, 197, 94, 0.12);
color: var(--green);
}
.qs-badge.partial {
background: rgba(245, 158, 11, 0.15);
color: var(--amber);
}
.qs-badge.todo {
background: rgba(245, 158, 11, 0.15);
color: var(--amber);
}
.qs-badge.blocker {
background: rgba(239, 68, 68, 0.15);
color: var(--red);
}
/* Responsive */
@media (max-width: 640px) {
.flow::before { left: 19px; }
.step-marker { width: 40px; height: 40px; min-width: 40px; font-size: 15px; }
.step-content { padding: 16px; }
.sanctions { grid-template-columns: 1fr; }
.tabs { flex-wrap: nowrap; }
}
</style>
</head>
<body>
<div class="noise"></div>
<div class="container">
<header>
<div class="badge">Normativa Laboral España 2026</div>
<h1>Flujo de <span>Fichaje y Firma</span></h1>
<p class="subtitle">Diseño de flujo compliant con el Art. 34.9 del Estatuto de los Trabajadores, RDL 8/2019 y el nuevo decreto de registro horario digital obligatorio.</p>
</header>
<div class="tabs">
<button class="tab active" onclick="showTab('fichaje')">Fichaje Diario</button>
<button class="tab" onclick="showTab('firma')">Firma Semanal</button>
<button class="tab" onclick="showTab('regulacion')">Regulación</button>
<button class="tab" onclick="showTab('sanciones')">Compliance Qamarero</button>
<button class="tab" onclick="showTab('roadmap')">Roadmap</button>
</div>
<!-- TAB 1: FICHAJE DIARIO -->
<div class="flow-section active" id="tab-fichaje">
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--accent)"></div> Acción del trabajador</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--amber)"></div> Validación del sistema</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div> Registro guardado</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--red)"></div> Alerta / Error</div>
</div>
<!-- Qamarero status summary for this flow -->
<div class="qamarero-status" style="margin-bottom: 24px;">
<div class="qs-header">
<div class="qs-title">
<span class="qs-logo">Q</span>
Estado del flujo de fichaje en Qamarero
</div>
<div class="qs-summary">3 cubiertos · 1 parcial · 2 prioritarios · 1 pendiente</div>
</div>
<div class="qs-grid">
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Fichaje entrada / salida</div>
<div class="qs-item-desc">Implementado. Fichaje digital desde la app con timestamp.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Validación en tiempo real</div>
<div class="qs-item-desc">Implementado. Se valida contra turno planificado.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Registro de pausas</div>
<div class="qs-item-desc">Implementado vía diferencia entre fichajes de entrada y salida.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item partial">
<div class="qs-icon">◐</div>
<div class="qs-body">
<div class="qs-item-title">Detección de horas extra</div>
<div class="qs-item-desc">Solo visual: contador en tiempo real en UI cuando se superan horas planificadas. No se persiste, no hay gestión de compensación ni aprobación.</div>
</div>
<div class="qs-badge partial">Parcial</div>
</div>
<div class="qs-item blocker">
<div class="qs-icon">✕</div>
<div class="qs-body">
<div class="qs-item-title">Inmutabilidad y traza de auditoría</div>
<div class="qs-item-desc">No implementado. Turnos y fichajes son editables por el manager sin dejar registro. Esto es un blocker de compliance — prioritario.</div>
</div>
<div class="qs-badge blocker">Prioritario</div>
</div>
<div class="qs-item blocker">
<div class="qs-icon">✕</div>
<div class="qs-body">
<div class="qs-item-title">Soft-delete: anulación de fichajes erróneos</div>
<div class="qs-item-desc">No implementado. Los fichajes se pueden eliminar (hard-delete). Se necesita soft-delete con estado anulado, motivo y traza. Vinculado al audit log.</div>
</div>
<div class="qs-badge blocker">Prioritario</div>
</div>
<div class="qs-item partial">
<div class="qs-icon">◐</div>
<div class="qs-body">
<div class="qs-item-title">Gestión de incidencias</div>
<div class="qs-item-desc">Hay feedback visual en la UI, pero las incidencias no se persisten ni se almacenan como registro formal.</div>
</div>
<div class="qs-badge partial">Parcial</div>
</div>
</div>
</div>
<div class="flow">
<div class="step">
<div class="step-marker action">1</div>
<div class="step-content">
<div class="step-title">Fichaje de ENTRADA <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">El camarero abre la app y pulsa <strong>"Fichar entrada"</strong>. El sistema captura automáticamente: <strong>timestamp exacto (hora:minuto)</strong>, ID del trabajador y centro de trabajo. El fichaje debe ser <strong>personal e intransferible</strong> — cada trabajador con sus propias credenciales (PIN, QR, tarjeta NFC). <strong>Prohibido:</strong> biometría (huella/rostro) salvo que no exista alternativa, según la AEPD.</div>
</div>
</div>
<div class="step">
<div class="step-marker check">⟐</div>
<div class="step-content">
<div class="step-title">Validación en tiempo real <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">El sistema valida: ¿El trabajador tiene turno asignado hoy? ¿Está dentro del margen horario razonable? Si hay <strong>desviación significativa</strong> respecto al turno planificado, se genera una <strong>incidencia automática</strong> (retraso / fichaje fuera de horario). El registro queda <strong>inmutable</strong> desde el momento de la captura.</div>
</div>
</div>
<div class="step">
<div class="step-marker action">2</div>
<div class="step-content">
<div class="step-title">Registro de PAUSAS <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">Aunque no es estrictamente obligatorio, el decreto reforzado de 2026 exige <strong>distinguir horas efectivas de trabajo, pausas, y horas extra</strong>. En hostelería, registrar el descanso intrajornada es clave para calcular correctamente las horas efectivas y evitar disputas. En Qamarero se calcula actualmente por <strong>diferencia entre fichajes de entrada y salida</strong>.</div>
</div>
</div>
<div class="step">
<div class="step-marker action">3</div>
<div class="step-content">
<div class="step-title">Fichaje de SALIDA <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">Al terminar el turno, el camarero pulsa <strong>"Fichar salida"</strong>. El sistema registra el timestamp exacto. Se calcula automáticamente: <strong>horas totales</strong>, <strong>horas efectivas</strong> (descontando pausas), y si hay <strong>horas extra</strong> respecto a la jornada planificada.</div>
</div>
</div>
<div class="step">
<div class="step-marker check">⟐</div>
<div class="step-content">
<div class="step-title">Detección de horas extra <span class="step-label" style="background:rgba(245,158,11,0.15); color:var(--amber);">Parcial</span></div>
<div class="step-desc">Si las horas efectivas <strong>superan la jornada contratada</strong>, el sistema marca automáticamente las horas excedentes como <strong>horas extraordinarias</strong>. Se debe documentar cómo se compensan: <strong>retribución económica o descanso compensatorio</strong>. El manager debe aprobar/justificar las horas extra.<br><br><strong style="color:var(--amber);">◐ En Qamarero:</strong> Existe un <strong>contador visual en tiempo real</strong> en la UI que muestra cuando se superan las horas planificadas. Sin embargo: el dato <strong>no se persiste</strong> ni se almacena como registro formal de horas extra, <strong>no hay flujo de compensación</strong> (dinero vs descanso), <strong>no hay aprobación</strong> del manager, y <strong>no se refleja en nómina</strong>. Es puramente informativo. Para compliance se necesita: persistir las horas extra detectadas, registrar tipo de compensación, y flujo de aprobación formal.</div>
</div>
</div>
<div class="step">
<div class="step-marker alert">✕</div>
<div class="step-content">
<div class="step-title">Registro diario completado <span class="step-label" style="background:rgba(239,68,68,0.15); color:var(--red);">Prioritario</span></div>
<div class="step-desc">Se genera el <strong>registro diario completo</strong>: entrada, pausas, salida, horas efectivas, horas extra (si aplica). El dato queda <strong>sellado digitalmente</strong> — cualquier corrección posterior deja <strong>traza de auditoría</strong> (quién, qué, cuándo). El trabajador puede consultar su fichaje en cualquier momento desde la app.<br><br><strong style="color:var(--red);">✕ En Qamarero:</strong> Actualmente tanto los turnos planificados como los fichajes son <strong>mutables por el manager sin dejar registro</strong> de la edición. Esto es un <strong>blocker de compliance</strong> — sin traza de auditoría, ante Inspección el registro puede considerarse manipulable y perder toda validez. Acción: implementar audit log inmutable (quién editó, valor anterior → nuevo, timestamp, motivo).</div>
</div>
</div>
<div class="step">
<div class="step-marker alert">✕</div>
<div class="step-content">
<div class="step-title">Anulación de fichajes erróneos (soft-delete) <span class="step-label" style="background:rgba(239,68,68,0.15); color:var(--red);">Prioritario</span></div>
<div class="step-desc">
<strong>Caso típico:</strong> un camarero ficha 3 veces en un día por error de UI (entrada, salida, entrada accidental). Uno de esos fichajes sobra y hay que corregirlo.<br><br>
<strong>Principio legal:</strong> un fichaje nunca se borra (hard-delete). Se marca como <strong>"anulado"</strong> con motivo y traza. Ante Inspección, un registro que desaparece sin rastro es peor que no tener registro — un fichaje anulado con justificación cuenta una historia coherente y transparente.<br><br>
<strong>Flujo requerido:</strong><br>
— El manager (o empleado + aprobación manager) marca el fichaje erróneo como <strong>anulado</strong>.<br>
— Se registra: motivo (<code>error_fichaje_empleado</code>, <code>duplicado</code>, <code>correccion_manager</code>, etc.), quién lo anuló, timestamp de la anulación.<br>
— El fichaje original <strong>permanece en BBDD</strong> con status <code>anulado</code> — nunca <code>DELETE FROM</code>.<br>
— En la <strong>UI normal</strong>: los fichajes anulados aparecen tachados o colapsados, sin ensuciar la vista del día.<br>
— En la <strong>vista de auditoría / exportación</strong> para Inspección: se muestra todo — activos y anulados con su traza completa.<br>
— El empleado recibe <strong>notificación</strong> de que su fichaje fue anulado para poder reclamar si no está de acuerdo.<br><br>
<strong>Modelo de datos sugerido:</strong> campo <code>status</code> en cada fichaje con valores <code>activo</code> | <code>modificado</code> | <code>anulado</code>, más campo <code>motivo_anulacion</code> y referencia al audit log.
<br><br>
<strong style="color:var(--red);">✕ En Qamarero:</strong> Actualmente los fichajes se eliminan con <strong>hard-delete</strong>. Se necesita migrar a soft-delete con estados + motivos + traza. Este cambio está directamente vinculado al audit log del punto anterior — ambos deberían implementarse juntos.
</div>
</div>
</div>
<div class="step">
<div class="step-marker check">◐</div>
<div class="step-content">
<div class="step-title">Gestión de olvidos / incidencias <span class="step-label" style="background:rgba(245,158,11,0.15); color:var(--amber);">Parcial</span></div>
<div class="step-desc">Si el trabajador olvida fichar, el manager puede <strong>crear un registro manual</strong>, pero: debe quedar <strong>trazado como edición manual</strong>, requiere <strong>justificación escrita</strong>, y el trabajador debe <strong>confirmarlo</strong>. El sistema nunca puede permitir editar un fichaje sin dejar rastro — eso invalida todo el registro ante Inspección.<br><br><strong style="color:var(--amber);">◐ En Qamarero:</strong> Se da <strong>feedback visual en la UI</strong> cuando hay olvidos o desviaciones, pero las incidencias <strong>no se almacenan</strong> como registro formal. Para compliance, cada incidencia debería persistirse con: tipo, timestamp, estado de resolución y quién la resolvió.</div>
</div>
</div>
</div>
</div>
<!-- TAB 2: FIRMA SEMANAL/MENSUAL -->
<div class="flow-section" id="tab-firma">
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div> Generación automática</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--accent)"></div> Acción del trabajador</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div> Documento firmado</div>
</div>
<div class="flow">
<div class="step">
<div class="step-marker data">①</div>
<div class="step-content">
<div class="step-title">Generación del resumen periódico <span class="step-label">Automático</span></div>
<div class="step-desc">Al cierre de cada <strong>semana o mes</strong> (tú decides la cadencia; semanal es más seguro en hostelería), el sistema genera un <strong>resumen de fichajes</strong> del trabajador: días trabajados, hora entrada/salida de cada día, pausas, horas totales, horas extra y su compensación.</div>
</div>
</div>
<div class="step">
<div class="step-marker action">②</div>
<div class="step-content">
<div class="step-title">Revisión por el trabajador <span class="step-label">Trabajador</span></div>
<div class="step-desc">El trabajador recibe una <strong>notificación push</strong> para revisar su resumen. Tiene un <strong>plazo definido</strong> (ej. 48-72h) para revisarlo. Puede ver el detalle de cada día y <strong>reclamar discrepancias</strong> antes de firmar. Si hay reclamación, se abre un <strong>flujo de incidencia</strong> que el manager debe resolver.</div>
</div>
</div>
<div class="step">
<div class="step-marker action">③</div>
<div class="step-content">
<div class="step-title">Firma digital del resumen <span class="step-label">Trabajador</span></div>
<div class="step-desc">El trabajador pulsa <strong>"Firmar y conformar"</strong>. La firma debe ser: <strong>personal</strong> (con las credenciales del trabajador, no la del manager), <strong>trazable</strong> (timestamp + IP/dispositivo), <strong>vinculante</strong> (el resumen firmado tiene valor probatorio ante tribunales — un TSJ de Madrid ya ha dictaminado que el registro firmado prevalece sobre WhatsApps). No es necesario firma manuscrita digital; basta un <strong>consentimiento explícito digital</strong> tipo "Confirmo que los datos son correctos".</div>
</div>
</div>
<div class="step">
<div class="step-marker check">⟐</div>
<div class="step-content">
<div class="step-title">Firma del responsable <span class="step-label">Manager</span></div>
<div class="step-desc">Opcionalmente (pero muy recomendable), el <strong>encargado o gerente</strong> también firma/conforma el resumen. Esto genera un <strong>documento bilateral</strong> que protege tanto al trabajador como a la empresa. Si el manager detecta incidencias no reportadas, puede añadir <strong>observaciones</strong> antes de firmar.</div>
</div>
</div>
<div class="step">
<div class="step-marker success">✓</div>
<div class="step-content">
<div class="step-title">Resumen sellado y archivado <span class="step-label">4 años</span></div>
<div class="step-desc">El resumen firmado se archiva como <strong>documento inmutable</strong>. Debe estar disponible para: el <strong>trabajador</strong> (acceso directo desde app), los <strong>representantes legales</strong> de los trabajadores, y la <strong>Inspección de Trabajo</strong> (acceso remoto en tiempo real, sin necesidad de que la empresa lo envíe). Conservación mínima: <strong>4 años</strong>.</div>
</div>
</div>
<div class="step">
<div class="step-marker alert">!</div>
<div class="step-content">
<div class="step-title">¿Qué pasa si no firma? <span class="step-label">Importante</span></div>
<div class="step-desc">Si el trabajador <strong>no firma en plazo</strong>, el sistema debe registrar que se le notificó y no actuó. El registro sigue siendo válido (la firma del trabajador no es requisito legal para la validez del registro, pero sí añade mucho valor probatorio). Si <strong>se niega reiteradamente</strong> a fichar o firmar, la empresa puede aplicar medidas disciplinarias, ya que es una obligación laboral del empleado.</div>
</div>
</div>
</div>
</div>
<!-- TAB 3: REGULACIÓN -->
<div class="flow-section" id="tab-regulacion">
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--red)"></div> Obligatorio por ley</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--amber)"></div> Muy recomendado</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--text-dim)"></div> Opcional</div>
</div>
<div class="flow">
<div class="step">
<div class="step-marker data">📋</div>
<div class="step-content">
<div class="step-title">Cada registro diario debe contener</div>
<div class="data-fields">
<span class="field"><span class="dot required"></span> ID trabajador</span>
<span class="field"><span class="dot required"></span> Fecha</span>
<span class="field"><span class="dot required"></span> Hora inicio jornada</span>
<span class="field"><span class="dot required"></span> Hora fin jornada</span>
<span class="field"><span class="dot recommended"></span> Inicio pausa</span>
<span class="field"><span class="dot recommended"></span> Fin pausa</span>
<span class="field"><span class="dot recommended"></span> Horas efectivas</span>
<span class="field"><span class="dot recommended"></span> Horas extra (si aplica)</span>
<span class="field"><span class="dot recommended"></span> Compensación h. extra</span>
<span class="field"><span class="dot recommended"></span> Centro de trabajo</span>
<span class="field"><span class="dot optional"></span> Tipo de turno</span>
<span class="field"><span class="dot optional"></span> Observaciones</span>
</div>
</div>
</div>
<div class="step">
<div class="step-marker data">🔐</div>
<div class="step-content">
<div class="step-title">Requisitos técnicos del sistema (Decreto 2026)</div>
<div class="step-desc">
<strong>Digital obligatorio</strong> — nada de papel ni Excel.<br>
<strong>Tiempo real</strong> — el fichaje se registra en el momento, no a posteriori.<br>
<strong>Inmutable</strong> — los datos no se pueden modificar sin dejar traza de auditoría.<br>
<strong>Accesible</strong> — el trabajador consulta sus fichajes en cualquier momento.<br>
<strong>Interoperable</strong> — Inspección de Trabajo puede acceder en remoto sin intervención de la empresa.<br>
<strong>Credenciales individuales</strong> — usuario+clave, PIN, QR o NFC (no biometría).<br>
<strong>Cifrado</strong> — en tránsito y en reposo, con control de accesos y backups.
</div>
</div>
</div>
<div class="step">
<div class="step-marker data">🏪</div>
<div class="step-content">
<div class="step-title">Particularidades hostelería</div>
<div class="step-desc">
<strong>Turnos partidos</strong> — muy comunes en restaurantes. Cada turno necesita su propio fichaje entrada/salida (ej.: 12:00-16:00 + 20:00-00:00 = 2 fichajes de entrada y 2 de salida).<br>
<strong>Tiempo parcial</strong> — obligatorio entregar resumen mensual de horas junto con la nómina (Art. 12.4.c ET).<br>
<strong>Extras y eventos</strong> — las horas trabajadas en eventos especiales deben registrarse igualmente.<br>
<strong>Jornada flexible</strong> — aunque haya flexibilidad, el horario real debe quedar registrado con precisión.
</div>
</div>
</div>
<div class="step">
<div class="step-marker data">💾</div>
<div class="step-content">
<div class="step-title">Conservación y almacenamiento: 4 años mínimo</div>
<div class="step-desc">Los registros deben conservarse durante <strong>4 años</strong> desde la fecha del fichaje. Deben estar disponibles de forma inmediata para Inspección, trabajadores y sus representantes legales. La integridad de los datos debe poder <strong>demostrarse</strong> (hashing, sellos de tiempo, etc.).</div>
</div>
</div>
<div class="step">
<div class="step-marker data">👁️</div>
<div class="step-content">
<div class="step-title">Acceso en tiempo real — 3 niveles</div>
<div class="step-desc">
<strong>Trabajador:</strong> accede a sus propios fichajes desde la app en cualquier momento. Puede descargar informes.<br>
<strong>Representantes legales (RLT):</strong> acceso a los registros de toda la plantilla, respetando el RGPD.<br>
<strong>Inspección de Trabajo:</strong> acceso remoto directo sin que la empresa tenga que enviar nada. Este es uno de los cambios más fuertes del decreto 2026.
</div>
</div>
</div>
<div class="step">
<div class="step-marker check">🛡️</div>
<div class="step-content">
<div class="step-title">Cumplimiento RGPD</div>
<div class="step-desc">
El registro horario es <strong>tratamiento de datos personales</strong>. Necesitas: <strong>informar al trabajador</strong> sobre el tratamiento (qué datos, para qué, quién accede), <strong>limitar accesos</strong> según rol, <strong>proteger contra accesos indebidos</strong>, y <strong>no usar el registro para monitorizar comportamiento</strong> más allá del control horario. La geolocalización NO es obligatoria y solo se justifica si el puesto lo requiere.
</div>
</div>
</div>
</div>
</div>
<!-- TAB 5: SANCIONES -->
<div class="flow-section" id="tab-sanciones">
<div class="sanctions">
<div class="sanction leve">
<div class="level">Leve</div>
<div class="amount">751 € — 1.500 €</div>
<div class="per">por trabajador afectado</div>
</div>
<div class="sanction grave">
<div class="level">Grave</div>
<div class="amount">1.501 € — 6.250 €</div>
<div class="per">por trabajador afectado</div>
</div>
<div class="sanction muy-grave">
<div class="level">Muy Grave</div>
<div class="amount">6.251 € — 10.000 €</div>
<div class="per">por trabajador afectado</div>
</div>
</div>
<!-- QAMARERO STATUS PANEL -->
<div class="qamarero-status">
<div class="qs-header">
<div class="qs-title">
<span class="qs-logo">Q</span>
Estado de compliance en Qamarero
</div>
<div class="qs-summary">3 de 4 riesgos cubiertos — <span class="qs-pending">1 pendiente</span></div>
</div>
<div class="qs-grid">
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Registro horario digital</div>
<div class="qs-item-desc">Implementado. Fichaje de entrada/salida desde la app.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Conservación 4 años</div>
<div class="qs-item-desc">Implementado. Los registros se persisten y conservan.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item covered">
<div class="qs-icon">✓</div>
<div class="qs-body">
<div class="qs-item-title">Sin papel ni Excel</div>
<div class="qs-item-desc">Implementado. Todo el registro es 100% digital.</div>
</div>
<div class="qs-badge done">Cubierto</div>
</div>
<div class="qs-item pending">
<div class="qs-icon">⚡</div>
<div class="qs-body">
<div class="qs-item-title">Traza de auditoría anti-manipulación</div>
<div class="qs-item-desc">Pendiente. Necesitamos audit log inmutable: quién editó, qué cambió, cuándo. Sin esto, cualquier corrección manual invalida el registro ante Inspección.</div>
</div>
<div class="qs-badge todo">Pendiente</div>
</div>
</div>
</div>
<h3 style="font-size:15px; font-weight:700; margin: 28px 0 16px; color: var(--text-dim);">Detalle de riesgos por incumplimiento</h3>
<div class="flow">
<div class="step">
<div class="step-marker success">✓</div>
<div class="step-content">
<div class="step-title">No tener registro horario <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">Se considera <strong>infracción grave</strong> según el Art. 7.5 de la LISOS. Además, si no hay registros válidos, se presume que el trabajador a tiempo parcial ha trabajado a <strong>jornada completa</strong>, lo que puede suponer pagos retroactivos de salarios y cotizaciones a la Seguridad Social.</div>
</div>
</div>
<div class="step">
<div class="step-marker alert">⚠</div>
<div class="step-content">
<div class="step-title">Registro manipulado o falsificado <span class="step-label" style="background:rgba(239,68,68,0.15); color:var(--red);">Pendiente</span></div>
<div class="step-desc">Obligar a los empleados a <strong>falsear el registro</strong> es motivo de condena judicial (hay sentencias). Un sistema que permita editar fichajes sin traza de auditoría puede interpretarse como <strong>facilitación de la manipulación</strong>. Riesgo muy alto ante Inspección.<br><br><strong style="color:var(--amber);">⚡ Acción requerida en Qamarero:</strong> Implementar audit log inmutable. Cada edición de un fichaje debe registrar: usuario que edita, valor anterior, valor nuevo, timestamp y motivo. Sin posibilidad de borrar la traza.</div>
</div>
</div>
<div class="step">
<div class="step-marker success">✓</div>
<div class="step-content">
<div class="step-title">No conservar los registros 4 años <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">Aunque los registros estén correctos, si no se conservan <strong>4 años</strong> o no están disponibles de forma inmediata para Inspección, también es sancionable. La conservación es parte obligatoria de la normativa, no un extra.</div>
</div>
</div>
<div class="step">
<div class="step-marker success">✓</div>
<div class="step-content">
<div class="step-title">Usar papel o Excel (desde 2026) <span class="step-label" style="background:rgba(34,197,94,0.15); color:var(--green);">Cubierto</span></div>
<div class="step-desc">Con la entrada en vigor del nuevo decreto, los métodos manuales quedan <strong>prohibidos</strong> como sistema principal. Solo serían válidos temporalmente en caso de <strong>incidencia técnica justificada</strong>. Qamarero ya es 100% digital — este requisito está cubierto.</div>
</div>
</div>
</div>
</div>
<!-- TAB 6: ROADMAP -->
<div class="flow-section" id="tab-roadmap">
<div class="flow">
<div class="step">
<div class="step-marker alert" style="font-size:14px; font-weight:800;">P0</div>
<div class="step-content" style="border-color: rgba(239,68,68,0.3);">
<div class="step-title">Audit log inmutable + soft-delete <span class="step-label" style="background:rgba(239,68,68,0.15); color:var(--red);">Blocker</span></div>
<div class="step-desc">
<strong>Por qué es P0:</strong> Sin esto, todo el registro de fichaje puede considerarse manipulable ante Inspección y pierde validez legal. Es el único punto que puede invalidar lo que ya tenemos.<br><br>
<strong>Qué implica:</strong><br>
— Tabla de audit log: fichaje_id, acción (creación/modificación/anulación), valor anterior, valor nuevo, usuario, timestamp, motivo.<br>
— Migrar de hard-delete a soft-delete en fichajes y turnos: campo <code>status</code> (<code>activo</code> | <code>modificado</code> | <code>anulado</code>) + <code>motivo_anulacion</code>.<br>
— Cualquier edición de manager deja traza inmutable. Nunca <code>DELETE FROM</code>.<br>
— UI: fichajes anulados visibles como tachados en vista normal, completos en vista auditoría.<br>
— Notificación al empleado cuando su fichaje es modificado o anulado.<br><br>
<strong>Nota:</strong> Audit log y soft-delete deberían implementarse juntos — son la misma pieza de infraestructura.
</div>
</div>
</div>
<div class="step">
<div class="step-marker alert" style="font-size:14px; font-weight:800;">P1</div>
<div class="step-content" style="border-color: rgba(245,158,11,0.3);">
<div class="step-title">Persistencia de horas extra <span class="step-label" style="background:rgba(245,158,11,0.15); color:var(--amber);">Compliance gap</span></div>
<div class="step-desc">
<strong>Por qué es P1:</strong> El contador visual existe pero no se persiste. Sin registro formal de horas extra, no se puede demostrar compliance en compensación ni ante Inspección ni ante el trabajador.<br><br>
<strong>Qué implica:</strong><br>
— Persistir las horas extra detectadas como registro vinculado al fichaje diario.<br>
— Campo de tipo de compensación: <code>retribucion_economica</code> | <code>descanso_compensatorio</code>.<br>
— Flujo de aprobación: el manager confirma/justifica las horas extra del día.<br>
— Las horas extra aprobadas deben reflejarse en el resumen semanal/mensual para firma.
</div>
</div>
</div>
<div class="step">
<div class="step-marker check" style="font-size:14px; font-weight:800;">P2</div>
<div class="step-content" style="border-color: rgba(245,158,11,0.3);">
<div class="step-title">Persistencia de incidencias <span class="step-label" style="background:rgba(245,158,11,0.15); color:var(--amber);">Compliance gap</span></div>
<div class="step-desc">
<strong>Por qué es P2:</strong> Hay feedback visual pero sin registro formal. Si un trabajador reclama por un olvido de fichaje, no hay evidencia de que la incidencia existió ni de cómo se resolvió.<br><br>
<strong>Qué implica:</strong><br>
— Modelo de incidencia: tipo (olvido fichaje, retraso, fichaje fuera de horario, fichaje erróneo), timestamp, estado (<code>abierta</code> | <code>resuelta</code> | <code>rechazada</code>), quién la resolvió y cómo.<br>
— Vincular incidencias al fichaje o día afectado.<br>
— Historial de incidencias accesible para el empleado.
</div>
</div>
</div>
<div class="step">
<div class="step-marker action" style="font-size:14px; font-weight:800;">P3</div>
<div class="step-content">
<div class="step-title">Firma semanal/mensual del trabajador <span class="step-label">Feature nueva</span></div>
<div class="step-desc">
<strong>Por qué es P3:</strong> No es obligatorio por ley, pero el valor probatorio del registro firmado es muy superior al no firmado (jurisprudencia TSJ Madrid). Reduce drásticamente el riesgo en disputas laborales.<br><br>
<strong>Qué implica:</strong><br>
— Generación automática de resumen periódico (semanal o mensual).<br>
— Notificación push al trabajador para revisión + firma digital.<br>
— Consentimiento explícito tipo "Confirmo que los datos son correctos" con timestamp + dispositivo.<br>
— Opcionalmente, co-firma del manager.<br>
— Resumen firmado inmutable y archivado 4 años.
</div>
</div>
</div>
<div class="step">
<div class="step-marker data" style="font-size:14px; font-weight:800;">P4</div>
<div class="step-content">
<div class="step-title">Acceso remoto para Inspección de Trabajo <span class="step-label">Decreto 2026</span></div>
<div class="step-desc">
<strong>Por qué es P4:</strong> Es un requisito del nuevo decreto, pero aún no ha entrado en vigor y hay incertidumbre sobre la fecha exacta. Conviene tenerlo en el radar pero no es urgente hoy.<br><br>
<strong>Qué implica:</strong><br>
— Endpoint o portal donde Inspección pueda acceder a los registros sin intervención del restaurante.<br>
— Formato y protocolo aún por definir por el Ministerio — pendiente de especificación técnica oficial.<br>
— Se estima plazo de 20 días para adaptación tras publicación en BOE.
</div>
</div>
</div>
<div class="step">
<div class="step-marker data" style="font-size:14px; font-weight:800;">P5</div>
<div class="step-content">
<div class="step-title">Vista de auditoría y exportación <span class="step-label">Operacional</span></div>
<div class="step-desc">
<strong>Por qué es P5:</strong> Una vez implementados P0-P2, toda la data estará ahí. Este punto es la capa de presentación para que los restaurantes (y eventualmente Inspección) puedan visualizar y exportar el historial completo con trazas de auditoría, anulaciones e incidencias de forma clara.<br><br>
<strong>Qué implica:</strong><br>
— Vista de auditoría que muestre fichajes activos + anulados + modificados con su traza.<br>
— Exportación a PDF/CSV compliant para presentar ante Inspección o asesores.<br>
— Filtros por empleado, rango de fechas, tipo de incidencia.
</div>
</div>
</div>
</div>
</div>
<!-- LEGAL REFERENCES -->
<div class="legal-ref">
<h3>📖 Base legal aplicable</h3>
<div class="legal-grid">
<div class="legal-card">
<div class="tag">Vigente desde 2019</div>
<div class="title">Art. 34.9 Estatuto de los Trabajadores</div>
<div class="desc">Obligación de registro diario con hora de inicio y fin. Conservación 4 años. Acceso para trabajadores, RLT e Inspección.</div>
</div>
<div class="legal-card">
<div class="tag">Vigente desde 2019</div>
<div class="title">RDL 8/2019, Art. 10</div>
<div class="desc">Introduce la obligatoriedad universal del registro. Infracción grave por incumplimiento.</div>
</div>
<div class="legal-card">
<div class="tag">Tramitación urgente 2026</div>
<div class="title">Nuevo Decreto Registro Digital</div>
<div class="desc">Digitalización obligatoria, acceso remoto para Inspección, detalle de pausas/horas extra, inmutabilidad, trazabilidad.</div>
</div>
<div class="legal-card">
<div class="tag">Sanciones</div>
<div class="title">LISOS — Art. 7.5 y Art. 40.1</div>
<div class="desc">Infracciones graves: 751€ a 7.500€ por trabajador. Se multiplican en incumplimientos generalizados.</div>
</div>
<div class="legal-card">
<div class="tag">Protección de datos</div>
<div class="title">RGPD + Guía AEPD</div>
<div class="desc">Biometría prohibida salvo sin alternativa. Geolocalización solo si justificada. Informar al trabajador del tratamiento.</div>
</div>
<div class="legal-card">
<div class="tag">Tiempo parcial</div>
<div class="title">Art. 12.4.c ET</div>
<div class="desc">Resumen mensual de horas junto con la nómina. Sin registro, se presume jornada completa.</div>
</div>
</div>
</div>
</div>
<script>
function showTab(id) {
document.querySelectorAll('.flow-section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + id).classList.add('active');
event.target.classList.add('active');
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment