reglas: el panel explica que se incumple y como arreglarlo; stats por etiqueta

- RuleViolation.reasons: el motor devuelve ahora QUE regla rompe cada
  asignacion (repetido en la semana, maximo por semana superado, no
  consecutivo, no mismo dia, solo comida/cena, solo entre semana o fin
  de semana) con su etiqueta y limite
- Panel de avisos: bloque con cada regla rota en lenguaje claro + una
  sugerencia de que hacer, y boton nuevo "Cambiar plato" que abre el
  selector de ese hueco (antes solo se podia quitar o ignorar)
- Estadisticas: matriz "Etiquetas por tipo de comida" (p.ej. cuantas
  cenas son de pescado) y "Platos por etiqueta" del catalogo
- Tests: 4 casos de las razones de incumplimiento; harness que renderiza
  las secciones nuevas de stats a /tmp/stats_render.png
- Strings en 6 idiomas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
This commit is contained in:
alexandrev-tibco
2026-09-10 21:06:42 +02:00
parent 9406c3f8e7
commit 718eef16bf
14 changed files with 545 additions and 11 deletions
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Schon zu Hause";
"premium_privacy_link" = "Datenschutzrichtlinie";
"premium_terms_link" = "Nutzungsbedingungen (EULA)";
"violation_reason_repeated" = "Dieses Gericht ist diese Woche schon eingeplant";
"violation_reason_max_per_week" = "Maximal %1$@: %2$d pro Woche — überschritten";
"violation_reason_no_consecutive" = "%@ darf nicht an aufeinanderfolgenden Tagen wiederholt werden";
"violation_reason_no_same_day" = "%@ ist an diesem Tag schon in einer anderen Mahlzeit";
"violation_reason_meal_only" = "%1$@ ist nur bei %2$@ erlaubt";
"violation_reason_day_scope" = "%1$@ ist nur %2$@ erlaubt";
"violation_scope_weekdays" = "unter der Woche";
"violation_scope_weekend" = "am Wochenende";
"violation_suggestion_repeated" = "Tausche es gegen ein anderes Gericht";
"violation_suggestion_swap" = "Tausche es gegen ein Gericht mit anderem Tag";
"violation_suggestion_move" = "Verschiebe es auf einen erlaubten Tag oder eine erlaubte Mahlzeit";
"violation_suggestion_generic" = "Gericht ändern oder Hinweis ignorieren";
"violations_replace" = "Gericht ändern";
"stats_tag_by_meal_title" = "Tags nach Mahlzeit";
"stats_dishes_per_tag_title" = "Gerichte pro Tag";
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Already at home";
"premium_privacy_link" = "Privacy Policy";
"premium_terms_link" = "Terms of Use (EULA)";
"violation_reason_repeated" = "This dish is already planned in another meal this week";
"violation_reason_max_per_week" = "Max %1$@: %2$d per week, and you are over it";
"violation_reason_no_consecutive" = "%@ cannot repeat on consecutive days";
"violation_reason_no_same_day" = "%@ is already in another meal on this day";
"violation_reason_meal_only" = "%1$@ is only allowed at %2$@";
"violation_reason_day_scope" = "%1$@ is only allowed %2$@";
"violation_scope_weekdays" = "on weekdays";
"violation_scope_weekend" = "on weekends";
"violation_suggestion_repeated" = "Swap it for another dish to vary the week";
"violation_suggestion_swap" = "Swap it for a dish with a different tag";
"violation_suggestion_move" = "Move it to a day or meal where it is allowed";
"violation_suggestion_generic" = "Change the dish or dismiss the warning";
"violations_replace" = "Change dish";
"stats_tag_by_meal_title" = "Tags by meal type";
"stats_dishes_per_tag_title" = "Dishes per tag";
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Ya en casa";
"premium_privacy_link" = "Política de privacidad";
"premium_terms_link" = "Términos de uso (EULA)";
"violation_reason_repeated" = "Este plato ya está en otra comida de la semana";
"violation_reason_max_per_week" = "Máximo %1$@: %2$d por semana, y ya lo superas";
"violation_reason_no_consecutive" = "%@ no puede repetirse en días seguidos";
"violation_reason_no_same_day" = "%@ ya está en otra comida de este mismo día";
"violation_reason_meal_only" = "%1$@ solo está permitido en %2$@";
"violation_reason_day_scope" = "%1$@ solo está permitido %2$@";
"violation_scope_weekdays" = "entre semana";
"violation_scope_weekend" = "en fin de semana";
"violation_suggestion_repeated" = "Cámbialo por otro plato para variar la semana";
"violation_suggestion_swap" = "Cámbialo por un plato de otra etiqueta";
"violation_suggestion_move" = "Muévelo a un día o comida donde sí esté permitido";
"violation_suggestion_generic" = "Cambia el plato o ignora el aviso";
"violations_replace" = "Cambiar plato";
"stats_tag_by_meal_title" = "Etiquetas por tipo de comida";
"stats_dishes_per_tag_title" = "Platos por etiqueta";
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Déjà à la maison";
"premium_privacy_link" = "Politique de confidentialité";
"premium_terms_link" = "Conditions d'utilisation (CLUF)";
"violation_reason_repeated" = "Ce plat est déjà prévu dans un autre repas cette semaine";
"violation_reason_max_per_week" = "Maximum %1$@ : %2$d par semaine, et vous le dépassez";
"violation_reason_no_consecutive" = "%@ ne peut pas se répéter deux jours de suite";
"violation_reason_no_same_day" = "%@ est déjà dans un autre repas ce jour-là";
"violation_reason_meal_only" = "%1$@ est autorisé uniquement au %2$@";
"violation_reason_day_scope" = "%1$@ est autorisé uniquement %2$@";
"violation_scope_weekdays" = "en semaine";
"violation_scope_weekend" = "le week-end";
"violation_suggestion_repeated" = "Remplacez-le par un autre plat pour varier";
"violation_suggestion_swap" = "Remplacez-le par un plat d'une autre étiquette";
"violation_suggestion_move" = "Déplacez-le vers un jour ou un repas autorisé";
"violation_suggestion_generic" = "Changez le plat ou ignorez l'alerte";
"violations_replace" = "Changer de plat";
"stats_tag_by_meal_title" = "Étiquettes par type de repas";
"stats_dishes_per_tag_title" = "Plats par étiquette";
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Già a casa";
"premium_privacy_link" = "Informativa sulla privacy";
"premium_terms_link" = "Termini di utilizzo (EULA)";
"violation_reason_repeated" = "Questo piatto è già previsto in un altro pasto della settimana";
"violation_reason_max_per_week" = "Massimo %1$@: %2$d a settimana, e lo hai superato";
"violation_reason_no_consecutive" = "%@ non può ripetersi in giorni consecutivi";
"violation_reason_no_same_day" = "%@ è già in un altro pasto di questo giorno";
"violation_reason_meal_only" = "%1$@ è consentito solo a %2$@";
"violation_reason_day_scope" = "%1$@ è consentito solo %2$@";
"violation_scope_weekdays" = "nei giorni feriali";
"violation_scope_weekend" = "nel fine settimana";
"violation_suggestion_repeated" = "Sostituiscilo con un altro piatto per variare";
"violation_suggestion_swap" = "Sostituiscilo con un piatto di un'altra etichetta";
"violation_suggestion_move" = "Spostalo in un giorno o pasto consentito";
"violation_suggestion_generic" = "Cambia il piatto o ignora l'avviso";
"violations_replace" = "Cambia piatto";
"stats_tag_by_meal_title" = "Etichette per tipo di pasto";
"stats_dishes_per_tag_title" = "Piatti per etichetta";
@@ -445,3 +445,18 @@
"shopping_at_home_section" = "Já em casa";
"premium_privacy_link" = "Política de privacidade";
"premium_terms_link" = "Termos de uso (EULA)";
"violation_reason_repeated" = "Este prato já está planejado em outra refeição desta semana";
"violation_reason_max_per_week" = "Máximo %1$@: %2$d por semana, e você já passou";
"violation_reason_no_consecutive" = "%@ não pode se repetir em dias seguidos";
"violation_reason_no_same_day" = "%@ já está em outra refeição deste mesmo dia";
"violation_reason_meal_only" = "%1$@ só é permitido no %2$@";
"violation_reason_day_scope" = "%1$@ só é permitido %2$@";
"violation_scope_weekdays" = "durante a semana";
"violation_scope_weekend" = "no fim de semana";
"violation_suggestion_repeated" = "Troque por outro prato para variar a semana";
"violation_suggestion_swap" = "Troque por um prato de outra etiqueta";
"violation_suggestion_move" = "Mova para um dia ou refeição permitidos";
"violation_suggestion_generic" = "Mude o prato ou ignore o aviso";
"violations_replace" = "Trocar prato";
"stats_tag_by_meal_title" = "Etiquetas por tipo de refeição";
"stats_dishes_per_tag_title" = "Pratos por etiqueta";