Skip to content

Commit 9909bd7

Browse files
authored
Merge pull request #37 from Llorente29/feature/auditoria-cierre
Feature/auditoria cierre
2 parents 11aa05a + 663c291 commit 9909bd7

7 files changed

Lines changed: 50 additions & 24 deletions

File tree

src/modules/appcc/pages/AppccDashboardPage.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ import type {
4141
} from '@/modules/appcc/services/analyticsService'
4242
import { SEVERITY_LABEL } from '@/modules/appcc/types'
4343

44-
// Paleta para gráficos (consistente con tokens semánticos)
44+
// Paleta para gráficos — auditoría externa (B.3): usaba rojo/ámbar
45+
// genéricos de Tailwind en vez de los tokens de marca (danger #E0492E,
46+
// warning #C2890F, tailwind.config.js). Ahora coherente con el resto del
47+
// producto (KPI cards, badges de estado).
4548
const SEV_COLORS: Record<string, string> = {
46-
critical: '#DC2626',
47-
high: '#F59E0B',
48-
medium: '#FBBF24',
49-
low: '#15171A',
49+
critical: '#E0492E', // danger
50+
high: '#C2890F', // warning
51+
medium: '#DCAA4D', // warning, más claro (distinto de "high" sin salirse de la familia ámbar)
52+
low: '#15171A', // accent/tinta
5053
}
5154
const CATEGORY_COLOR = '#15171A'
5255
const TREND_COLOR = '#15171A'
@@ -238,7 +241,7 @@ export default function AppccDashboardPage() {
238241
/>
239242
<Tooltip
240243
contentStyle={{ fontSize: 12, borderRadius: 6 }}
241-
formatter={(v) => [`${v}%`, 'Cumplimiento']}
244+
formatter={(v) => [v == null ? 'Sin auditoría este día' : `${v}%`, 'Cumplimiento']}
242245
labelFormatter={(l) => `Día ${String(l ?? '')}`}
243246
/>
244247
<Line

src/modules/appcc/services/analyticsService.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ export interface DailyComplianceData {
4242
date: string // YYYY-MM-DD
4343
total: number // ejecuciones del día
4444
completed: number // completadas
45-
rate: number // % (0-100)
45+
/** % (0-100). null = sin ejecuciones ese día ("sin dato"), NO 0% — un día
46+
* sin controles programados no es un día de incumplimiento. Auditoría
47+
* externa (B.1): antes se rellenaba con 0, y la línea caía a cero todos
48+
* los días sin auditoría, leyendo como "este local no cumple casi nunca"
49+
* cuando el KPI de al lado decía 93%. Con null, recharts <Line> no dibuja
50+
* el tramo (no conecta a través de huecos por defecto) -- puntos solo en
51+
* los días con auditoría real, en vez de un electrocardiograma falso. */
52+
rate: number | null
4653
}
4754

4855
/** Distribución de incidencias por severidad */
@@ -223,7 +230,7 @@ export async function getDailyCompliance(
223230
date: iso,
224231
total: e?.total ?? 0,
225232
completed: e?.completed ?? 0,
226-
rate: e && e.total > 0 ? Math.round((e.completed / e.total) * 100) : 0,
233+
rate: e && e.total > 0 ? Math.round((e.completed / e.total) * 100) : null,
227234
})
228235
}
229236

src/modules/kitchen/components/EconomiaTab.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,13 @@ export default function EconomiaTab({ item }: EconomiaTabProps) {
132132
<div className="text-[11px] text-stone-400">IVA {fmtPct(vatPct)} incluido</div>
133133
</div>
134134
<div className="bg-stone-50 rounded-lg px-4 py-3">
135-
<div className="text-[10px] font-medium text-stone-400 tracking-widest uppercase mb-1">Coste de producto</div>
135+
{/* "Food cost" se queda EN INGLÉS a propósito (decisión de Julio,
136+
auditoría externa): es el término estándar del sector en toda
137+
la hostelería española, igual que "escandallo" o "mise en
138+
place" -- traducirlo suena más pulcro pero resta credibilidad
139+
profesional. Distinto de "Plate cost"/"Packaging", que sí eran
140+
spanglish descuidado y se tradujeron. No lo "arregles" de vuelta. */}
141+
<div className="text-[10px] font-medium text-stone-400 tracking-widest uppercase mb-1">Food cost</div>
136142
<div className={`font-mono text-lg font-medium ${hasCost ? 'text-[#BA7517]' : 'text-stone-300'}`}>{hasCost ? fmtEur(recipeCost) : '—'}</div>
137143
<div className="text-[11px] text-stone-400">{hasCost ? `${fmtPct(foodCostPct, 2)} del PVP` : 'Pendiente de escandallo'}</div>
138144
</div>
@@ -177,11 +183,11 @@ export default function EconomiaTab({ item }: EconomiaTabProps) {
177183
<div className="flex items-center gap-2.5">{badge}</div>
178184
<div className="text-right">
179185
<span className={`font-mono text-xl font-medium ${margin >= 0 ? 'text-success' : 'text-danger'}`}>{fmtEur(margin)}</span>
180-
<div className="text-[12px] text-stone-400">{fmtPct(marginPct, 2)} del PVP{!e.costAvailable ? ' · sin coste de producto' : ''}</div>
186+
<div className="text-[12px] text-stone-400">{fmtPct(marginPct, 2)} del PVP{!e.costAvailable ? ' · sin food cost' : ''}</div>
181187
</div>
182188
</div>
183189
<div className="flex flex-wrap gap-x-4 gap-y-1 mb-2 text-[12px] text-stone-500">
184-
{e.costAvailable && <span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-sm bg-[#A68B6B]" /> Coste de producto {fmtEur(e.cost)}</span>}
190+
{e.costAvailable && <span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-sm bg-[#A68B6B]" /> Food cost {fmtEur(e.cost)}</span>}
185191
{e.commissionPct != null && <span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-sm bg-[#4A6A8A]" /> Comisión {fmtPct(e.commissionPct)} ({fmtEur(commAmt)})</span>}
186192
{hasOrderCosts && (
187193
<span className="flex items-center gap-1.5 cursor-help"
@@ -215,8 +221,8 @@ export default function EconomiaTab({ item }: EconomiaTabProps) {
215221
prop, así que no hay ventana de desincronización). */}
216222
<p className="text-[12px] text-stone-500 mt-4 pt-3 border-t border-stone-200">
217223
{item.targetFoodCostPct != null
218-
? `Objetivo de coste: ${fmtPct(item.targetFoodCostPct)} · ${foodCostPct != null ? (foodCostPct <= item.targetFoodCostPct ? 'Dentro del objetivo' : 'Fuera del objetivo') : 'sin coste de producto para comparar'}`
219-
: 'Sin objetivo de coste configurado.'}
224+
? `Objetivo de food cost: ${fmtPct(item.targetFoodCostPct)} · ${foodCostPct != null ? (foodCostPct <= item.targetFoodCostPct ? 'Dentro del objetivo' : 'Fuera del objetivo') : 'sin food cost para comparar'}`
225+
: 'Sin objetivo de food cost configurado.'}
220226
</p>
221227
</div>
222228
)

src/modules/kitchen/components/FichaTab.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,13 @@ export default function FichaTab({ item, accountId, onItemChanged }: FichaTabPro
465465

466466
{/* Objetivo de food cost (target_food_cost_pct) — EDITABLE, única pieza
467467
de lógica nueva permitida en esta fase. La comparación "Dentro/Fuera
468-
del objetivo" sigue viviendo en Economía. */}
468+
del objetivo" sigue viviendo en Economía.
469+
"Food cost" se queda en inglés a propósito (decisión de Julio,
470+
auditoría externa) -- término estándar del sector, no spanglish
471+
descuidado. Ver EconomiaTab.tsx para la nota completa. */}
469472
<div>
470-
<h3 className="text-xs font-medium uppercase tracking-wide text-stone-400 mb-3">Objetivo de coste de producto</h3>
471-
<label className="block text-xs font-medium text-stone-500 mb-1.5">Objetivo de coste (%)</label>
473+
<h3 className="text-xs font-medium uppercase tracking-wide text-stone-400 mb-3">Objetivo de food cost</h3>
474+
<label className="block text-xs font-medium text-stone-500 mb-1.5">Objetivo de food cost (%)</label>
472475
<input
473476
type="text" inputMode="decimal" value={targetFcVal} onChange={(e) => setTargetFcVal(e.target.value)}
474477
placeholder="Sin definir"

src/modules/kitchen/components/RecipeEscandalloTab.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,15 +2190,15 @@ export default function RecipeEscandalloTab({
21902190
que eso signifique "no está en carta" — en ese caso, aviso
21912191
distinto, sin el CTA de "Añadir a carta" (ya está añadido). */}
21922192
{econLoading || usedByItems === null ? (
2193-
<div className="text-[11px] text-text-secondary">Calculando coste de producto</div>
2193+
<div className="text-[11px] text-text-secondary">Calculando food cost</div>
21942194
) : usedByItems.length === 0 ? (
21952195
<div>
21962196
<div className="text-[11px] font-medium tracking-wide text-text-secondary uppercase mb-2">
2197-
Coste de producto
2197+
Food cost
21982198
</div>
21992199
<div className="flex items-start gap-1.5 text-[11px] text-text-secondary leading-relaxed mb-2.5">
22002200
<AlertTriangle className="w-3.5 h-3.5 mt-px flex-shrink-0 text-warning" />
2201-
<span>Este plato aún no está en ninguna carta. Añádelo para ver su coste de producto y margen.</span>
2201+
<span>Este plato aún no está en ninguna carta. Añádelo para ver su food cost y margen.</span>
22022202
</div>
22032203
<button
22042204
type="button"
@@ -2212,9 +2212,9 @@ export default function RecipeEscandalloTab({
22122212
) : economics.length === 0 ? (
22132213
<div>
22142214
<div className="text-[11px] font-medium tracking-wide text-text-secondary uppercase mb-2">
2215-
Coste de producto
2215+
Food cost
22162216
</div>
2217-
<p className="text-[11px] text-text-secondary">Sin datos de coste de producto por canal todavía.</p>
2217+
<p className="text-[11px] text-text-secondary">Sin datos de food cost por canal todavía.</p>
22182218
</div>
22192219
) : (
22202220
<div className="flex flex-col gap-3">

src/modules/kitchen/pages/CatalogFichaPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import ConfirmDialog from '@/components/ConfirmDialog'
6666
import * as coursesService from '@/services/coursesService'
6767
import { generateCourseFromRecipe, type GenerateCourseResult } from '@/services/courseFromRecipeService'
6868
import type { MenuItem, RecipeItem } from '@/types/kitchen'
69+
import { fmtPct } from '@/lib/format'
6970

7071
// ─── Helpers ────────────────────────────────────────────────────────────────
7172

@@ -958,7 +959,7 @@ export default function CatalogFichaPage({
958959
? 'Sin coste'
959960
: !pvp
960961
? 'Sin precio'
961-
: `${foodCostPct}%`}
962+
: fmtPct(foodCostPct, 2)}
962963
</div>
963964
<div className="text-xs text-stone-500">food cost</div>
964965
</div>

src/pages/VentasDashboardPage.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ export default function VentasDashboardPage() {
410410
<span>{l.name}</span>
411411
<span className="text-stone-500 tabular-nums">{eur(l.net)} · {l.orders} ped.</span>
412412
</div>
413-
<Bar pct={(l.net / locationMax) * 100} color="#534AB7" />
413+
<Bar pct={(l.net / locationMax) * 100} color={TERRA} />
414414
</div>
415415
))}
416416
</div>
@@ -427,7 +427,13 @@ export default function VentasDashboardPage() {
427427
{data.by_hour.map((h) => {
428428
const ratio = h.net / hourMax
429429
const height = Math.max(6, ratio * 64)
430-
const color = ratio > 0.66 ? '#534AB7' : ratio > 0.33 ? '#7F77DD' : '#CECBF6'
430+
// Auditoría externa (B.2): violeta (#534AB7/#7F77DD) sin
431+
// relación con la marca, contiguo a "Ventas por canal" (negro/
432+
// tinta) justo encima -- dos idiomas de color en la misma
433+
// pantalla, la primera de la demo comercial. Misma escala
434+
// monocroma que el resto (tailwind.config.js: "rebrand tinta
435+
// monocroma").
436+
const color = ratio > 0.66 ? TERRA : ratio > 0.33 ? '#6B7077' : '#E9EBED'
431437
return (
432438
<div key={h.hour} className="flex-1 text-center" title={`${eur(h.net)} · ${h.orders} ped.`}>
433439
<div style={{ height, background: color, borderRadius: 3 }} />

0 commit comments

Comments
 (0)