feat(pos/workshop): add lightweight workshop/taller module

- Add DB migration v4.4_workshop.sql (sale_id, service_catalog,
  reserved_quantity, SO_RESERVE/SO_RELEASE operation types).
- Extend service_order_engine with inventory reservation, release,
  convert-to-sale, mechanic assignment, and service catalog CRUD.
- Extend service_order_bp with /reserve, /convert-to-sale,
  /assign-mechanic, and /service-catalog endpoints.
- Create workshop Kanban UI: workshop.html, workshop.js, workshop.css.
- Add /pos/workshop route and sidebar navigation (sidebar.js + inline
  templates).
- Add 11 unit tests with mocked cursors.
- Update FASES_IMPLEMENTADAS.md with FASE 9 documentation.

Tests: 92 passing (61 console + 20 Facturapi + 11 workshop).
This commit is contained in:
2026-06-15 05:34:35 +00:00
parent d67887284d
commit ce66212223
15 changed files with 1842 additions and 14 deletions

221
pos/static/css/workshop.css Normal file
View File

@@ -0,0 +1,221 @@
:root {
--kanban-column-width: 280px;
--kanban-card-bg: var(--glass-bg-strong);
--kanban-card-border: var(--glass-border);
}
.page-header__subtitle {
color: var(--color-text-muted);
font-size: var(--text-sm);
margin-top: var(--space-1);
}
.kanban-board {
display: flex;
gap: var(--space-4);
overflow-x: auto;
padding-bottom: var(--space-4);
min-height: 60vh;
}
.kanban-column {
flex: 0 0 var(--kanban-column-width);
display: flex;
flex-direction: column;
max-height: 75vh;
}
.kanban-column__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3) var(--space-4);
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md) var(--radius-md) 0 0;
font-family: var(--font-heading);
font-size: var(--text-sm);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
letter-spacing: var(--tracking-wide);
}
.kanban-column__count {
background: var(--color-primary);
color: var(--color-bg);
font-size: var(--text-xs);
padding: 2px 8px;
border-radius: var(--radius-full);
}
.kanban-column__body {
flex: 1;
overflow-y: auto;
background: rgba(0, 0, 0, 0.15);
border: 1px solid var(--glass-border);
border-top: none;
border-radius: 0 0 var(--radius-md) var(--radius-md);
padding: var(--space-3);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.kanban-card {
background: var(--kanban-card-bg);
border: 1px solid var(--kanban-card-border);
border-radius: var(--radius-md);
padding: var(--space-3);
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.kanban-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.kanban-card__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--space-2);
}
.kanban-card__id {
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--color-primary);
font-weight: var(--font-weight-bold);
}
.kanban-card__priority {
font-size: var(--text-xs);
padding: 2px 6px;
border-radius: var(--radius-sm);
text-transform: uppercase;
font-weight: var(--font-weight-bold);
}
.kanban-card__priority--urgent { background: var(--color-error); color: #fff; }
.kanban-card__priority--high { background: var(--color-warn); color: #000; }
.kanban-card__priority--normal { background: var(--color-info); color: #fff; }
.kanban-card__customer {
font-weight: var(--font-weight-bold);
margin-bottom: var(--space-1);
}
.kanban-card__vehicle {
font-size: var(--text-sm);
color: var(--color-text-muted);
margin-bottom: var(--space-2);
}
.kanban-card__meta {
display: flex;
justify-content: space-between;
font-size: var(--text-xs);
color: var(--color-text-muted);
}
.kanban-card__mechanic {
display: flex;
align-items: center;
gap: var(--space-1);
}
/* Detail modal */
.so-detail {
display: grid;
gap: var(--space-4);
}
.so-detail__section {
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: var(--space-4);
}
.so-detail__section h3 {
font-family: var(--font-heading);
font-size: var(--text-sm);
text-transform: uppercase;
letter-spacing: var(--tracking-wide);
margin-bottom: var(--space-3);
}
.so-detail__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-3);
}
.so-detail__field {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.so-detail__label {
font-size: var(--text-xs);
color: var(--color-text-muted);
text-transform: uppercase;
}
.so-detail__value {
font-weight: var(--font-weight-bold);
}
.so-detail__actions {
display: flex;
gap: var(--space-3);
flex-wrap: wrap;
}
/* Tables inside modal */
.data-table--compact {
width: 100%;
border-collapse: collapse;
font-size: var(--text-sm);
}
.data-table--compact th,
.data-table--compact td {
padding: var(--space-2) var(--space-3);
text-align: left;
border-bottom: 1px solid var(--glass-border);
}
.data-table--compact th {
color: var(--color-text-muted);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
font-size: var(--text-xs);
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--text-xs);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
}
.status-badge--pending { background: var(--color-warn); color: #000; }
.status-badge--reserved { background: var(--color-info); color: #fff; }
.status-badge--installed { background: var(--color-success); color: #000; }
@media (max-width: 1024px) {
.kanban-board {
flex-direction: column;
overflow-x: visible;
}
.kanban-column {
flex: 1 1 auto;
max-height: none;
}
}