# Bug- und Wunsch-Portal

Web-Portal, über das **Endkunden** Fehler und Verbesserungswünsche zum neuen Shop
einreichen. Laravel 12, MariaDB, kein Node/Build-Schritt (handgeschriebenes CSS
unter `public/css/app.css`).

Alle Texte sind aus Sicht eines einkaufenden Privatkunden formuliert, nicht aus
Betreiber- oder Entwicklersicht. Wer die Oberfläche ändert, sollte das beibehalten:
keine Fachbegriffe wie „Reproduktionsschritte“, keine B2B-Felder wie Firma oder
Filiale. Die Auswirkungsskala in `Ticket::IMPACTS` reicht deshalb von
„Schönheitsfehler“ bis „Ich kann nicht bestellen oder bezahlen“.

## Gestaltung

Farben, Schriften und Logo sind von riccardo-zigarette.de übernommen:

| | |
|:--|:--|
| Leitfarbe | `#eb0025`, aktiv/fokussiert `#9c0f25` |
| Kopfzeile | Verlauf `#181717` → `#2c2c2c`, weißes Logo, roter Abschluss |
| Text | `#2c2c2c`, sekundär `#6e6e6e` |
| Schrift | Barlow Semi Condensed (Überschriften, Bedienelemente), Barlow (Fließtext) |

Der Shop setzt Fließtext auf `#949494`; auf Weiß sind das nur 2,8:1 Kontrast, was
für Formularbeschriftungen zu wenig ist – deshalb hier der abgedunkelte Ton.

Die Schriften liegen als auf Latin reduzierte WOFF2-Dateien unter `public/fonts/`
(zusammen 77 KB statt 500 KB als TTF). Kein externer Font-Dienst, keine
Hotlinks auf den Shop. Neu erzeugen lassen sie sich mit `pyftsubset` aus den
TTF-Originalen des Shops.

## Wie es aus Kundensicht funktioniert

1. Kunde ruft `/` auf und füllt das Formular aus – kein Account nötig.
2. Er bekommt eine Vorgangsnummer (`BUG-2026-0001` / `FR-2026-0001`) und einen
   persönlichen Statuslink `/status/{token}`, den er auch per Mail erhält.
   Optional kann er seine Bestellnummer angeben (`tickets.order_number`).
3. Auf der Statusseite sieht er den Bearbeitungsstand, unsere öffentlichen
   Antworten und kann selbst nachfassen.

Interne Notizen im Backend sind auf dieser Seite **nicht** sichtbar – dafür gibt
es das Flag `is_public` an jedem Kommentar.

## Kampagne „Riccardo Fehlerjagd"

Umgesetzt nach Regelwerk v2 (Council-Dokument V1.1, 07.09.2026). Alle Stellschrauben
stehen in `config/bugportal.php` unter `campaign` und lassen sich per `.env` ändern,
ohne Code anzufassen.

| Regel | Umsetzung |
|:--|:--|
| 3 % je bestätigtem Fehler, Deckel 30 % | `RewardService::credit()`, Deckel in `RewardAccount::isCapped()` |
| Erst die Bestätigung schreibt gut | eigener Status `confirmed` + `confirmed_at`, getrennt von `fixed` |
| Erstmelder gewinnt | `tickets.reported_at`, beim Zusammenführen gegen die Richtung geprüft |
| Bereits gelistet = kein Rabatt | Dublette verweist auf die Erstmeldung, `isRewardEligible()` verneint |
| Bagatellen zählen einmal | „Zusammenführen" im Backend, Vorschläge über `findPossibleDuplicates()` |
| Ablehnung mit Begründung | Pflichtfeld; die Begründung wird als öffentlicher Kommentar sichtbar |
| Rückmeldung in drei Werktagen | `responseDueAt()`, überfällige Meldungen im Backend gefiltert |
| Wünsche sind nicht rabattfähig | im Formular gekennzeichnet, `isRewardEligible()` schließt sie aus |
| Sicherheitslücken vertraulich | eigener Meldetyp, eigener Mailverteiler, nie öffentlich vor `fixed` |
| Nennung nur mit Einwilligung | getrenntes Häkchen + Ort, `creditLine()` gibt sonst `null` |
| Einlösung in einer Bestellung | `RewardService::redeem()` erzeugt einen `FEHLER-`-Code und setzt auf null |
| Aktionsende | `BUGPORTAL_CAMPAIGN_END`; danach laufen Meldungen weiter, ohne Gutschrift |

### Öffentliche Fehlerliste

`/gemeldet` zeigt freigegebene Meldungen in den drei Stufen des Regelwerks
(`gemeldet · in Arbeit · behoben`), durchsuchbar, plus ein Änderungsprotokoll der
zuletzt behobenen Fehler. Nichts erscheint dort automatisch: jede Meldung braucht
den Haken „Auf die öffentliche Liste setzen". Sicherheitsmeldungen lehnt das System
ab, solange sie nicht behoben sind.

### Fehlerkonto

Geführt über die Newsletter-Adresse, wie im Regelwerk vorgesehen. Der Kontostand
wird nicht gespeichert, sondern aus den einzelnen Buchungen gerechnet – so bleibt
jede Gutschrift nachvollziehbar. Verwaltung unter `/admin/fehlerkonten`.

### Noch offen

* **Teilnahmekette** (Kundenkonto + Newsletter-Double-Opt-in) wird derzeit im
  Backend von Hand gesetzt. Für den automatischen Abgleich fehlen die Zugangsdaten
  der Shopware-6-Admin-API und der Brevo-API.
* **Gutscheine** entstehen als Code im Portal; das Anlegen der Promotion in
  Shopware 6 per API ist vorbereitet, aber noch nicht angebunden.
* **Gültigkeit des Fehlerkontos** ist im Council-Dokument ein Platzhalter.
  Sobald die Zahl steht: `BUGPORTAL_CREDIT_VALIDITY_DAYS` setzen.
* **Pflichthinweis** (Nikotin-Warnhinweis oder 18+-Fallback) fehlt auf der Seite,
  bis die Vorgabe von Dr. Markgraf vorliegt.

## Backend

`/admin` – Liste mit Filtern (Status, Art, Priorität, Volltext), Detailansicht
mit Status-/Prioritätspflege, internen Notizen und Kundenantworten.

Zugang anlegen oder Passwort zurücksetzen:

```bash
php artisan bugportal:user name@firma.de --name="Vorname Nachname"
php artisan bugportal:user name@firma.de --password="…"   # Passwort selbst setzen
```

## Spamschutz

Ohne Captcha, damit die Hürde für Kunden niedrig bleibt:

* Honeypot-Feld `website` (unsichtbar, muss leer bleiben)
* Zeitprüfung: Absenden in unter 3 Sekunden wird abgelehnt
* Rate-Limit: 10 Meldungen bzw. 20 Antworten pro Stunde je IP
* Pflicht-E-Mail inkl. DNS-Prüfung der Domain (`BUGPORTAL_VALIDATE_EMAIL_DNS`)

## Anhänge

Bis zu 5 Dateien à 10 MB (Bilder, Videos, PDF, Text). Sie liegen **außerhalb**
des Webroots unter `storage/app/private/tickets/{id}/` und werden nur über
`/status/{token}/datei/{id}` bzw. das angemeldete Backend ausgeliefert.
Die passenden PHP-Limits stehen in `public/.user.ini` – die globale `php.ini`
bleibt unangetastet, damit `suite/` und `websale-connect/` nicht betroffen sind.

## Mails

SMTP-Zugang wird aus `.env` gelesen (übernommen aus `suite/.env`).

| Anlass | Empfänger |
|:--|:--|
| Neue Meldung / Kundenantwort | `BUGPORTAL_NOTIFY_TO` (kommagetrennt möglich) |
| Eingangsbestätigung | Melder |
| Statuswechsel, öffentliche Antwort | Melder (im Backend abwählbar) |

Ein fehlgeschlagener Mailversand wird protokolliert, verwirft die Meldung aber
nicht – der Kunde verliert seine Eingabe nie wegen eines SMTP-Problems.

## Betrieb

```bash
composer test                                       # 41 Feature-Tests
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
```

Nach Änderungen an `.env` oder `config/` muss `config:cache` erneut laufen.

Vhost-Vorlage: `docs/bugreport.apache.conf`.

## Konfiguration (`.env`)

| Schlüssel | Bedeutung |
|:--|:--|
| `BUGPORTAL_NOTIFY_TO` | Intern benachrichtigte Adressen, kommagetrennt |
| `BUGPORTAL_SHOP_NAME` | Shop-Name in Oberfläche und Mails |
| `BUGPORTAL_SHOP_URL` | Ziel des „Zurück zum Shop“-Links |
| `BUGPORTAL_CONTACT_EMAIL` | Kontaktadresse in der Fußzeile |
| `BUGPORTAL_VALIDATE_EMAIL_DNS` | DNS-Prüfung der Melder-Domain (Standard `true`) |
