# Contexto y actualización del proyecto — soporteHolanda

**Fecha de actualización:** 2026-07-24  
**Rama:** `main` (al día con `origin/main`; HEAD `227d466`)  
**Repo:** `git@gitlab.com:sistematlan/soporteholanda.git`  
**Dominio local:** `https://soporteholanda.test/` (Damp / FrankenPHP)

---

## 0. Avance del día (2026-07-24)

Resumen de lo trabajado y pusheado hoy (commits `931d16a`, `a4b31b3`, `227d466`; base previa `7af21cb` con multi-fallas / `reason` VARCHAR 255).

### Formulario público

| Cambio | Detalle |
|--------|---------|
| Wizard unificado | **3 pasos + éxito:** Tipo → Búsqueda (cadena+tienda) → **Solicitud** (confirmación de tienda + motivo + envío). Se eliminó el paso intermedio de “solo confirmar”. |
| Multi-select de fallas | Solo en **Autoservicio** (checkboxes, 1 o más). Se guarda en `reason` unido por `", "`. **Otros** = select único. *(Se corrigió: el multi estaba mal asignado a Otros.)* |
| Número de congelador | Campo `numero_congelador`: **obligatorio en Autoservicio**, máx. **6** caracteres. En **Otros** no se pide al usuario; el backend fuerza `00000`. |
| Ayuda congelador | Leyenda muted bajo el input: *Si no cuentas con el número de congelador coloca "000000" y pon el número de serie en comentarios.* |
| Motivo promociones | Eliminado de la lista de motivos (si existía en UI/docs previos). |
| Mensajes de éxito | Textos diferenciados por tipo (Autoservicio / Otros). |

### Backend / API

| Cambio | Detalle |
|--------|---------|
| `POST /api/tickets` | Acepta y valida `numero_congelador`. Multi-motivo validado solo si `store_type = autoservicio`. |
| `TicketModel` | `normalizeReason` / `isValidReason` multi → `TYPE_AUTOSERVICIO`. Constantes: `FREEZER_MAX_LENGTH = 6`, `FREEZER_DEFAULT_OTROS = '00000'`. |
| Migraciones | `ExpandTicketsReasonForMulti` (`reason` → VARCHAR 255); `AddNumeroCongeladorToTickets` (columna `numero_congelador` VARCHAR 32, default `00000`). |

### Dashboard

| Cambio | Detalle |
|--------|---------|
| Columna **Congelador** | Visible en DataTables y export (entre Motivo y Comentarios). |

### Docs

| Cambio | Detalle |
|--------|---------|
| `app/Views/docs.php` | Motivos: multi en Autoservicio, único en Otros. |
| Este archivo | Actualizado con estado vigente y avance del día. |

### Deploy / migraciones en ambientes ya existentes

- Si **ya** se corrieron las migraciones de multi-fallas y congelador: con **deploy de código** basta para el cambio multi → Autoservicio y el tope de 6 caracteres (no hay migración nueva por eso).
- Si el ambiente **no** tiene esas migraciones:

```bash
php spark migrate
# o, con Shield:
php spark migrate --all
```

Migraciones relevantes del 24-jul:

1. `2026-07-24-100000_ExpandTicketsReasonForMulti` — `tickets.reason` VARCHAR(255)  
2. `2026-07-24-120000_AddNumeroCongeladorToTickets` — columna `numero_congelador`

---

## 1. Propósito

Sistema de **soporte Holanda** para que puntos de venta levanten **tickets** desde un formulario público. Backoffice con Shield y DataTables para gestionar solicitudes.

Referencia de UI: **Contingencia** (formulario, login, dashboard, export DataTables), adaptado a Tailwind + primary Holanda `#DE2727`.

---

## 2. Stack

| Capa | Tecnología |
|------|------------|
| Backend | PHP ^8.2 / 8.3, CodeIgniter **4.7.4** |
| Auth | CodeIgniter Shield **1.4** |
| DB | MySQL `soporteholanda_db` (Damp `damp-db`) |
| Form público | Tailwind **4.3** + Select2 4.x + JS vanilla |
| Dashboard | DataTables 2.2 + Buttons + CSS BS5 estilo Contingencia |
| Primary | `#DE2727` |
| Contenedor contenido | `max-w-8xl` (90rem / 1440px, token custom) |
| URLs | `indexPage = ''` (sin `index.php`) |

### Comandos útiles

```bash
npm install && npm run build
docker exec soporteholanda php spark migrate
docker exec soporteholanda php spark migrate --all
docker exec soporteholanda php spark db:seed StoresSeeder
docker exec soporteholanda php spark shield:user create -n admin -e admin@soporteholanda.local -g superadmin
```

### Accesos de prueba (local)

| Campo | Valor |
|--------|--------|
| Login | `https://soporteholanda.test/login` |
| User | `admin` |
| Email | `admin@soporteholanda.local` |
| Password | `Admin123!` |
| Grupo | `superadmin` |
| Dashboard | `https://soporteholanda.test/dashboard` |

---

## 3. Rutas

| Método | Ruta | Acceso | Uso |
|--------|------|--------|-----|
| GET | `/` | Público | Wizard de solicitud |
| GET | `/docs` | Público | Ayuda / FAQ del sistema |
| GET/POST | `/login`, logout… | Shield | Auth |
| GET | `/dashboard` | `session` | Backoffice |
| GET | `/api/stores/cadenas?type=` | Público | Cadenas por tipo (Select2) |
| GET | `/api/stores/search?type=&cadena=&q=` | Público | Autocomplete tiendas |
| GET | `/api/stores?type=&cadena=&q=` | Público | Igual que search; **400 sin q** |
| GET | `/api/stores/{id}` | Público | Detalle tienda |
| GET | `/api/tickets/reasons?type=` | Público | Motivos por tipo |
| POST | `/api/tickets` | Público + CSRF | Crear ticket |
| GET | `/api/tickets` | `session` | Listado DataTables |
| POST | `/api/tickets/{id}/status` | `session` | Cerrar / cambiar estado |
| POST | `/api/tickets/status-batch` | `session` | Cerrar varios |

CSRF global. POST JSON con header `X-CSRF-TOKEN`. Tras POST AJAX se relee el token de respuesta (`X-CSRF-TOKEN`).

---

## 4. Modelo de datos

### 4.1 `stores`

| Campo | Notas |
|-------|--------|
| `type` | `autoservicio` \| `otros` (antes `farmacia`) |
| `clave`, `cadena`, `nombre`, `sap`, `direccion` | |
| `active` | 1/0 |

**Volúmenes actuales (seed):**

| type | Filas |
|------|------:|
| autoservicio | ~5 655 (`docs/stores_autoservicios.csv` ← `base_autoservicios.xlsx`) |
| otros | ~10 957 (`docs/stores_otros.csv` ← `base_others.xlsx`) |

**Mapeo Excel Otros (Hoja1):**

| Excel | stores |
|-------|--------|
| SOLD_TO | `clave` + `sap` |
| CADENA | `cadena` |
| NOMBRE | `nombre` |
| DIRECCIÓN | `direccion` |

Caché: solo `stores_cadenas_{type}` (TTL 7 días). Las búsquedas por `q` **no** se cachean.

Índice búsqueda: `(type, active, cadena)` — migración `AddStoresTypeActiveCadenaIndex`.

### API tiendas (server-side search)

| Endpoint | Notas |
|----------|--------|
| `GET /api/stores/cadenas?type=` | Lista corta de cadenas (Select2) |
| `GET /api/stores/search?type=&cadena=&q=` | Autocomplete: mín. 2 chars en `q`, limit 30 |
| `GET /api/stores?type=&cadena=&q=` | Igual que search; **400 sin q** (catálogo completo deshabilitado) |
| `GET /api/stores/{id}` | Detalle al confirmar tienda |

El form **no** descarga el catálogo completo de la cadena (evita timeouts en WM ~3k, 7 ELEVEN ~2k, etc.).

### 4.2 `tickets`

| Campo | Notas |
|-------|--------|
| `id` | PK autoincrement |
| **`uuid`** | Folio único: `SSS{id}` si autoservicio, `Others{id}` si otros |
| Snapshot tienda | `store_id`, `store_type`, `store_clave`, `store_cadena`, `store_nombre`, `store_sap`, `store_direccion` |
| `reason` | Motivo validado. En **autoservicio** puede ser varias fallas unidas por `", "` (VARCHAR **255**) |
| `comments` | Opcional, **máx. 100** caracteres |
| **`numero_congelador`** | VARCHAR 32 en BD. UI/API: máx. **6** en autoservicio (obligatorio). Otros: valor fijo `00000` |
| `status` | `pending` \| `in_progress` \| `closed` |
| `created_at` | Se setea al crear |
| `updated_at` | `null` al crear; se actualiza al cambiar estado |

### Migraciones (orden)

1. `CreateStoresTable`  
2. `CreateTicketsTable`  
3. `AddCommentsToTickets`  
4. `AddUuidAndRenameFarmaciaToOtros` — columna uuid + rename type + backfill  
5. `AddStoresTypeActiveCadenaIndex` — índice para search server-side  
6. `ExpandTicketsReasonForMulti` — `reason` VARCHAR(255) (multi-fallas)  
7. `AddNumeroCongeladorToTickets` — `numero_congelador`  

---

## 5. Formulario público (`form.php`)

### Stepper (3 pasos + éxito)

1. **Tipo** — Autoservicio \| Otros  
2. **Búsqueda** — cadena (Select2) + tienda (autocomplete server-side por clave o nombre)  
3. **Solicitud** — card de confirmación (clave, nombre, dirección, cadena; **sin SAP en UI**) + motivos + congelador (si aplica) + comentarios + enviar  
4. **Éxito** — mensaje Unilever con folio (texto según tipo)

Toasts de carga al pedir cadenas / detalle de tienda; `AbortController` para evitar carreras.

### Motivos

**Autoservicio (multi-select / checkboxes):** una o más fallas, se guardan unidas por comas:

- Falla de congelador  
- Cambio de imagen(stickers)  
- Cambio de tapas  
- Cambio de canastillas  
- Cambio de rejillas  
- Cambio de ruedas  
- Cambio de molduras  
- Cambio de cable (3mts máximo)  

**Otros (select único):**

- Falla de congelador  
- Problemas con pedidos  
- Reposición de producto  
- Cambio de datos  
- Otros  

### Número de congelador

| Tipo | Comportamiento |
|------|----------------|
| Autoservicio | Visible y obligatorio. Máx. **6** caracteres. Si no lo tienen: indicar `000000` y poner el **número de serie en comentarios**. |
| Otros | No se muestra. Backend persiste `00000`. |

### Mensaje de éxito (base)

```
¡Gracias por ponerte en contacto con nosotros! Hemos recibido tu solicitud.
Tu número de folio de seguimiento es: [UUID].
En breve un técnico te visitará. Si tienes alguna duda, requieres seguimiento o deseas escalar tu caso, escríbenos a ERTM.reporting@unilever.com.
```

(Variantes por tipo en el form si aplica.)

### Chrome UI

- Navbar / footer full width, primary `#DE2727`  
- Footer: `{año} | Hecho con mucho 🍦 en Sistematlan` (izquierda)  
- Contenido: `max-w-8xl`  
- Controles de form a **16px** para evitar zoom residual en iOS Safari (ver `docs/auditoria-responsive.md`)

---

## 6. Dashboard / backoffice

### Shell (`layout.php`)

- Sin sidebar; logo + título en navbar  
- Contenido `max-w-8xl`  
- Botón Formulario en **blanco** (override Bootstrap)  

### Stats (3 cards, color primary)

- Total tickets  
- Pendientes  
- Cerrados  
*(sin card “En proceso”)*

### DataTables (estilo Contingencia)

Assets en `public/assets/vendor/libs/datatables-*` + `public/css/datatables-theme.css`.

**Columnas (orden):**

| # | Columna | Notas |
|---|---------|--------|
| 0 | Checkbox | Select-all; cerrados disabled |
| 1 | Fecha | Solo fecha (sin hora); default order por ID desc (más nuevos arriba) |
| 2 | Folio | `uuid` |
| 3 | ID | **Oculta** |
| 4 | Tipo | Autoservicio / Otros |
| 5 | Clave | Si vacía / “Sin datos” → **Sin clave** |
| 6 | SAP | Visible en backoffice |
| 7 | Cadena | |
| 8 | Tienda | Solo nombre (**sin dirección**) |
| 9 | Motivo | Puede traer varias fallas (autoservicio) |
| 10 | **Congelador** | `numero_congelador` |
| 11 | Comentarios | Texto pequeño, **sin truncar** |
| 12 | Estado | Badge |
| 13 | Acciones | Modal comentarios + check cerrar |

**Toolbar:** Completar (batch) + Exportar (CSV / Excel / PDF / Copiar). Sin overlay ni toast de copiado.

**API estado:** single + batch → `closed` con confirmación.

---

## 7. Archivos clave

```
app/
  Config/Routes.php
  Controllers/Home.php, Dashboard.php, Docs.php
  Controllers/Api/StoreController.php, TicketController.php
  Models/StoreModel.php, TicketModel.php
  Database/Migrations/*stores*, *tickets*, *comments*, *uuid*, *Multi*, *Congelador*
  Database/Seeds/StoresSeeder.php
  Views/form.php, login.php, layout.php, dashboard.php, docs.php

resources/css/app.css     # primary + --container-8xl: 90rem + inputs 16px
public/css/app.css
public/css/datatables-theme.css
public/assets/vendor/libs/datatables-bs5|buttons|responsive/

docs/
  contexto-actualizacion.md   # este archivo
  analisis-inicial.md
  estructura-inicial.md
  tareas-pendientes.md
  stores-calidad-clave.md
  auditoria-responsive.md
  base_autoservicios.xlsx / stores_autoservicios.csv
  base_others.xlsx / stores_otros.csv
```

---

## 8. Decisiones vigentes

| Tema | Decisión |
|------|----------|
| Tipos de negocio | `autoservicio`, `otros` (ex-farmacia) |
| Folio | `SSS{id}` / `Others{id}` en `tickets.uuid` |
| Flujo form | Tipo → Búsqueda (cadena+tienda) → Solicitud (confirm + motivo + envío) |
| Motivos multi | Solo **Autoservicio** (checkboxes 1+); **Otros** = un solo select |
| Congelador | Obligatorio en autoservicio, máx. 6; Otros = `00000` fijo; ayuda con `000000` + serie en comentarios |
| Filtro tiendas | Por `type` + `cadena` + búsqueda server-side (`q` mín. 2) |
| SAP form | Oculto; guardado en snapshot |
| SAP dashboard | Visible |
| Comentarios | Máx. 100 en form; columna completa en DT |
| Orden DT | Más nuevos primero (id desc) |
| `reason` multi | Separados por `", "`; columna VARCHAR 255 |

---

## 9. Estado de implementación

| Módulo | Estado |
|--------|--------|
| Scaffold CI4 + Damp + Tailwind primary | ✅ |
| Shield + login + layout | ✅ |
| Catálogo stores (auto + otros) + seed | ✅ |
| API stores (cadenas, search, show; sin catálogo full) | ✅ |
| Wizard form (3 pasos) + Select2 + search server-side | ✅ |
| Create ticket + uuid + comments 100 | ✅ |
| Motivos multi Autoservicio / único Otros | ✅ |
| Número de congelador (form + API + dashboard) | ✅ |
| Mensaje éxito Unilever (por tipo) | ✅ |
| Docs públicas (`/docs`) | ✅ |
| Dashboard DataTables + columna congelador | ✅ |
| Batch completar + export | ✅ |
| Ajustes mobile zoom (16px controls) | ✅ |
| CRUD admin tiendas | ❌ |
| Deduplicación claves / calidad datos | ❌ |
| Filtros avanzados DT | ❌ |

---

## 10. Próximos pasos sugeridos

1. Filtros por estado/tipo/fecha en el dashboard.  
2. Estado “en proceso” en flujo operativo (si se necesita de nuevo).  
3. Limpieza de datos: 11 claves vacías, ~101 `"Sin datos"`, dups CHEDRAUI/FUTURAMA (ver `stores-calidad-clave.md`).  
4. Rate limit / captcha en `POST /api/tickets`.  
5. CRUD admin de tiendas + invalidación de caché de cadenas.  
6. (Opcional) Alinear valor fijo Otros `00000` (5 dígitos) con la ayuda de form `000000` (6 dígitos) si el negocio lo requiere.

---

## 11. Checklist de arranque

1. `composer install`  
2. `npm install && npm run build`  
3. `.env` con `app.baseURL` (trailing slash), DB, `encryption.key`  
4. Damp/Docker (red `damp`)  
5. `php spark migrate --all`  
6. `php spark db:seed StoresSeeder`  
7. Usuario Shield  
8. Probar form (auto multi-fallas + congelador; otros select único) y dashboard  

---

## 12. Commits recientes relevantes (24-jul)

| Hash | Mensaje |
|------|---------|
| `227d466` | limit to 6 chars in freezer number |
| `a4b31b3` | Corrige multi-select de fallas a Autoservicio y aclara el número de congelador |
| `931d16a` | Unifica confirmación y motivo en un paso, y agrega número de congelador |
| `7af21cb` | Toasts de carga, multi-fallas (originalmente en Otros), `reason` VARCHAR 255, paso «Búsqueda» |

---

*Documento de contexto para continuidad. Actualizar al cambiar APIs, wizard o backoffice.*
