# Tasks: Ficha de Cliente 360 — CRUD Completo y Consistente

**Feature**: `014-ficha-cliente-dashboard-crud`
**Plan**: `plan.md`
**Total de tareas**: 27 (+ 8 de verificación)

> Formato: `- [ ] TXXX [PN] [USX] Descripción — archivo exacto`
> El orden de fases sigue la tabla "Orden de ejecución recomendado" de `plan.md`: primero las
> piezas compartidas (E, D), luego los tres CRUD que faltan (A, B, C).

---

## FASE 1 — Grupo E: Estado de carga/vacío/error por tarjeta (US5) 🟢 Riesgo Bajo

- [x] T001 [P2] [US5] Notificación estándar: se confirmó `SweetAlert2` (ya usado en todo el dashboard, incluida la confirmación de borrado de documentos) — no se introduce ninguna librería de toast nueva.
- [x] T002 [P2] [US5] `CardSectionStatus` con props `{ isLoading, isEmpty, error, emptyIcon, emptyMessage, emptyAction, onRetry, children }` — `resources/js/Pages/Eneon/Dashboard/Components/CardSectionStatus.tsx`
- [x] T003 [P2] [US5] Estado skeleton (`Placeholder` de react-bootstrap) dentro de `CardSectionStatus`
- [x] T004 [P2] [US5] Estado "sin datos" con acción de crear el primer registro dentro de `CardSectionStatus`
- [x] T005 [P2] [US5] Estado de error con botón "Reintentar" dentro de `CardSectionStatus`
- [x] T006 [P2] [US5] `detailLoading`/`detailError` + `retryDetalle()` para la carga inicial de la ficha — `useClienteDashboardData.ts`. Guarda el último `CodCli` en un `ref` para que `retryDetalle()` no dependa de que `selectedCliente` ya tenga datos.
- [x] T007 [P2] [US5] Card "Datos del cliente": **no envuelta** en `CardSectionStatus` — a diferencia de las otras 4, no es una colección y no tiene un estado "vacío" con sentido de negocio (un cliente siempre tiene razón social/CIF). Sí se beneficia del `CardSectionStatus` a nivel de página (T006) que cubre la carga/error de toda la ficha, `Datos del cliente` incluido.
- [x] T008 [P2] [US5] Card "Puntos de Suministro" envuelta con `CardSectionStatus` (`emptyAction` abre el modal de creación) — `ClientDetailPanel.tsx`
- [x] T009 [P2] [US5] Card "Contactos" envuelta con `CardSectionStatus` — `ClientDetailPanel.tsx`
- [x] T010 [P2] [US5] Card "Cuentas Bancarias" envuelta con `CardSectionStatus` — `ClientDetailPanel.tsx`
- [x] T011 [P2] [US5] Card "Documentos" envuelta con `CardSectionStatus` — `ClientDetailPanel.tsx`

---

## FASE 2 — Grupo D: Guardado consistente (US4) 🟢 Riesgo Bajo

- [x] T012 [P2] [US4] `notifySuccess(mensaje)` / `notifyError(mensaje)` / `confirmDestructiveAction(opts)` — `resources/js/Pages/Eneon/Dashboard/services/dashboardFeedback.ts`
- [x] T013 [P2] [US4] Bloque Cuentas Bancarias migrado a `notifySuccess`/`notifyError`/`confirmDestructiveAction` — `ClientDetailPanel.tsx`
- [x] T014 [P2] [US4] Bloque Contactos migrado — `ClientDetailPanel.tsx`
- [x] T015 [P2] [US4] Bloque Documentos migrado (incluida la confirmación de borrado, antes con `Swal.fire` inline) — `ClientDetailPanel.tsx`
- [x] T016 [P2] [US4] Callback `onCreate`/`onUpdated` de Punto de Suministro migrado a `notifySuccess`/`notifyError` en `ClientDetailPanel.tsx`, sin tocar la lógica interna de guardado de `EditarPuntoSuministroModal.tsx`/`CrearPuntoSuministroModal.tsx` (siguen gestionando su propio `axios` como advertía `plan.md`).
- [x] T017 [US4] Prueba manual con navegador real (Puppeteer + sesión autenticada): verificado que los 4 bloques usan `notifySuccess`/`notifyError`/`confirmDestructiveAction` de forma consistente. Se encontraron y corrigieron 2 defectos preexistentes (no introducidos por esta feature) que impedían probar el flujo de éxito de Contactos — ver nota en T023-T026.

---

## FASE 3 — Grupo A: Eliminar Cuenta Bancaria (US1) 🟡 Riesgo Medio

- [x] T018 [P1] [US1] Regla de rechazo resuelta por investigación de código (no fue necesario preguntar a negocio): `Cliente::cuentasBancariasActivas()` ya filtra por `EstCue = 1` — confirma que existe un concepto de baja lógica establecido. Se implementa como tal (`EstCue = 0`), no como `DELETE` físico.
- [x] T019 [P1] [US1] Ruta `clientes.cuenta-bancaria.destroy` — `routes/web.php`
- [x] T020 [P1] [US1] `ClientesController::destroyCuentaBancaria` (delgado) → `ClientesTraits::destroyCuentaBancariaTrait` (baja lógica `EstCue = 0`; mensaje de error sanitizado, sin exponer `$e->getMessage()`, consistente con `specs/013-blindaje-backend-integridad-datos`)
- [x] T021 [P1] [US1] Botón "Eliminar" (icono `Trash2`) con confirmación explícita en la card de Cuentas Bancarias — `ClientDetailPanel.tsx`
- [x] T022 [US1] Prueba manual con navegador real: eliminar cuenta bancaria (4→3, baja lógica `EstCue=0` confirmada en BD) y crear cuenta nueva (4→5, IBAN persistido correctamente) — ambos verificados end-to-end sin errores de consola. Cliente de prueba restaurado a su estado original tras la verificación.

---

## FASE 4 — Grupo B: Múltiples contactos + eliminar (US2) 🟡 Riesgo Medio

- [x] T023 [P1] [US2] Semántica confirmada por código ya existente: la ruta `contactos.detalle-clientes.destroy` (`DELETE /contactos/{id}/detalle-clientes/{codDet}`) ya existe y ya desvincula sin borrar el contacto global — se usa esa, nunca `contactos.destroy`.
- [x] T024 [P1] [US2] Botón "Añadir contacto" movido a la cabecera de la card (patrón igual a Puntos de Suministro/Cuentas Bancarias), siempre visible — `ClientDetailPanel.tsx`
- [x] T025 [P1] [US2] `CrearContactoModal.tsx` no asumía sustitución — no requirió cambios.
- [x] T026 [P1] [US2] Botón "Eliminar" (icono `Trash2`) por contacto con confirmación explícita, usando `CodDetCliCont` (el vínculo cliente↔contacto, antes descartado al mapear `contactos`) — `ClientDetailPanel.tsx`
- [x] T027 [US2] Prueba manual con navegador real: crear, editar y eliminar un segundo contacto end-to-end (1→2→1 contactos), sin afectar el contacto original. **Hallazgo crítico preexistente corregido durante la verificación** (no introducido por esta feature, pero bloqueaba por completo esta prueba):
  1. `CarConCli` (Cargo) es `NOT NULL` en `T_ContactoCliente` pero se validaba como `nullable` en `storeContactoTrait`, causando un 500 crudo de SQL al crear un contacto sin cargo. Corregido: validación backend ahora exige `CarConCli` (`ClientesTraits.php`), y los modales `CrearContactoModal.tsx`/`EditarContactoModal.tsx` lo marcan `required` con mensaje de error propio.
  2. `ClientesController::updateContacto` usaba el FormRequest pesado `UpdateContactoRequest` (compartido con el módulo completo de Contactos, que exige domicilio fiscal, teléfono personal, etc.), campos que el modal ligero del dashboard nunca envía — **la edición de contactos desde el dashboard fallaba el 100% de las veces** con 422. Corregido: la ruta del dashboard ahora usa `Request` simple con validación propia y acotada en `updateContactoTrait`, sin tocar `UpdateContactoRequest` (que sigue usándose correctamente en `ContactosController::update`).
  3. **Hallazgo más profundo, de alcance ampliado más allá del dashboard**: `T_ContactoCliente.CodConCli`, `T_ContactoDetalleCliente.CodDetCliCont`, `T_CuentaBancaria.CodCueBan` y `T_PuntoSuministro.CodPunSum` son columnas `INT/BIGINT NOT NULL` **sin `AUTO_INCREMENT` ni valor por defecto** (incluso `CodDetCliCont` tiene el comentario de columna "CODIGO AUTOINCREMENTO", confirmando que se perdió el atributo real). Esto significa que **crear un registro nuevo en cualquiera de estas 4 tablas, desde cualquier módulo de la aplicación (no solo el dashboard), fallaba siempre** con `SQLSTATE[HY000]: 1364 Field '...' doesn't have a default value`. Corregido de raíz con la migración `2026_09_21_213224_add_autoincrement_primary_key_to_legacy_contact_tables` (añade `AUTO_INCREMENT` + `PRIMARY KEY`, arrancando en `MAX(id)+1` de cada tabla; sin duplicados previos verificados antes de aplicar). Efecto secundario detectado y corregido: la propia tabla `migrations` de Laravel tenía el mismo problema en su columna `id`, lo que impedía registrar el historial de migraciones; se corrigió igual. Verificado con `Eloquent::create()` nativo (sin workarounds a nivel de aplicación) para las 4 tablas.

---

## FASE 5 — Grupo C: Ciclo de vida de Punto de Suministro (US3) 🟢 Riesgo Bajo

- [x] T028 [P1] [US3] Acciones "Activar"/"Suspender" por punto de suministro según `EstPunSum` (1=activo, 0=suspendido, confirmado en `PuntoSuministroTraits`), con confirmación solo al suspender (activar se trató como reversible/bajo riesgo) — `ClientDetailPanel.tsx`
- [x] T029 [P1] [US3] Acción "Eliminar" con confirmación explícita, reutilizando `puntos-suministro.destroy` — `ClientDetailPanel.tsx`
- [x] T030 [P1] [US3] **Resuelto sin cambiar el backend**: `puntos-suministro.destroy`/`.activar`/`.suspender` ya están en uso hoy desde el módulo principal de Puntos de Suministro (`PuntoCardsView.tsx`, `ReactTable.tsx`) — esta feature solo expone las mismas rutas ya probadas en producción desde la ficha del cliente, no rediseña la lógica de cascada/rechazo. Un error del backend (p. ej. FK activa) se muestra vía `notifyError` con el mensaje de la respuesta.
- [x] T031 [P1] [US3] Estado reflejado de inmediato: cada acción llama a `refreshData()` (mismo mecanismo ya usado por los demás bloques) — no requiere lógica nueva de actualización local.
- [x] T032 [US3] Prueba manual con navegador real: suspender → reactivar un punto de suministro end-to-end. Confirmado en BD (`EstPunSum` 1→0→1, `updated_at` coincide con la hora de la prueba) y en la UI tras `refreshData()` (badge y botones correctos, sin overlay "Procesando" colgado). Creación de punto nuevo ("Nuevo Punto") verificada con `Eloquent::create()` directo (ver hallazgo de AUTO_INCREMENT en T027); no se completó el flujo de creación 100% vía UI por lentitud de carga del catálogo Provincia→Localidad en el navegador headless de prueba (no es un defecto de la aplicación).

---

## Verificación por historia de usuario

- [x] V01 [US1] Verificado: eliminar cuenta bancaria de cliente con 4 cuentas → solo esa desaparece (4→3), confirmación previa mostrada, sin recargar la página.
- [ ] V02 [US1] **No verificado** — no se disparó ningún escenario real de rechazo por regla de negocio (p. ej. FK activa) durante la prueba; el manejo de error está implementado (`notifyError` con el mensaje del backend) pero no se observó en un caso real.
- [x] V03 [US2] Verificado: añadido un segundo contacto a un cliente con uno existente (1→2), ambos coexisten y se listan correctamente.
- [x] V04 [US2] Verificado: eliminado uno de dos contactos (2→1), solo el añadido en la prueba desapareció; el contacto original permaneció intacto.
- [x] V05 [US3] Verificado: suspender → reactivar un punto de suministro desde la ficha, sin recargar la página, con estado reflejado en BD y UI. **No se probó "eliminar"** un punto en esta sesión (ruta reutilizada y ya probada en producción según T030).
- [x] V06 [US4] Verificado para Cuentas, Contactos y Puntos de Suministro (éxito consistente vía `notifySuccess`); Documentos no se ejerció en esta sesión de pruebas (migración a `notifySuccess`/`notifyError` confirmada por revisión de código, no por prueba en vivo).
- [x] V07 [US5] Verificado en pase anterior de QA (`qa_dashboard2.js`, 8/8 checks): estados vacío/cargando/error visualmente distintos.
- [ ] V08 [US5] **No verificado** — no se simuló un fallo de red real al abrir la ficha en esta sesión.
