# Implementation Plan: Rediseño visual del módulo SIPS

**Feature Branch**: `012-redisenio-ui-sips`

**Created**: 2026-07-09

**Status**: Draft

**Input**: `specs/012-redisenio-ui-sips/spec.md`

> **Constitution (II. Skinny Controllers, IV. Inertia.js Frontera)**: Este plan define CÓMO
> se implementará el rediseño en Laravel + React + Inertia.js. El alcance es **exclusivamente
> capa de presentación**; no se modifican contratos con proveedores ADX/AUDAX.

---

## Resumen técnico

El módulo SIPS (`resources/js/Pages/Eneon/Sips/`) consulta datos vía `POST route('sips.consumo')` (`SipsController::consumirServicioSips`) y renderiza dos áreas principales en paneles ADX/AUDAX:

1. **Datos Suministros** — hoy `AdxSuministroDetails.tsx` itera `Object.entries(suministro)` mostrando claves API crudas.
2. **Consumo** — `SipsConsumoResults.tsx` con tarjetas de resumen + tabla Bootstrap básica sin `scope`, sticky header ni tokens de Contratos.

### Problemas detectados en código actual

| Problema | Ubicación | Impacto |
|----------|-----------|---------|
| Claves API como etiquetas | `AdxSuministroDetails.tsx` | Usuario ve `Cod_Dist`, `Pot_Cont_P1`, etc. |
| Clase CSS inexistente `estilosFoco.scope` | `Index.tsx`, `AdxSipsPanel.tsx`, `AudaxSipsPanel.tsx` | Foco accesible no aplicado (existe `contenedorAccesibleContratos`) |
| Tabla consumo sin estándar Contratos | `SipsConsumoResults.tsx` | Sin `scope="col"`, sin cabecera sticky, estilos genéricos |
| Resumen duplicado | `AdxSipsPanel.tsx` | `SipsResumenLecturas` en tab Datos y en tab Consumo |
| Tipado débil suministros | `types.ts` → `Record<string, unknown>[]` | Sin catálogo ni validación de mapeo |

### Solución propuesta

```mermaid
flowchart TB
    subgraph consulta [Flujo sin cambios backend]
        A[AdxSipsPanel / AudaxSipsPanel] --> B[useSipsConsulta]
        B --> C[SipsController consumirServicioSips]
        C --> D[AdxSipsService normalizeResponse]
        D --> E[SipsResponse JSON]
    end

    subgraph presentacion [Nueva capa presentación]
        E --> F[suministroElectricoFields.ts catálogo 47 campos]
        F --> G[suministroMappers.ts fallbacks + formato]
        G --> H[SipsSuministroDetalle.tsx secciones + CampoInformativo]
        E --> I[SipsConsumoResults.tsx tabla accesible + tarjetas]
    end

    subgraph referencia [Reutilización Contratos]
        J[SubseccionFormulario] --> H
        K[CampoInformativo] --> H
        L[estilosFocoAccesible.module.css] --> A
        M[thStickyHeader pattern] --> I
    end
```

---

## User Review Required

> [!NOTE]
> **Decisiones aprobadas por el usuario (2026-07-09)**

| Pregunta | Decisión |
|----------|----------|
| ¿Reutilizar `CampoInformativo` / `SubseccionFormulario` de Contratos o mover a Shared? | **Reutilizar desde Contratos** (`UniClienteUniPunto/components/`) |
| ¿Quitar resumen de lecturas del tab «Datos Suministros»? | **No** — mantener resumen en ambos tabs |
| ¿AUDAX solo mensaje informativo? | **Ver sección «Alcance AUDAX»** — no sustituye el formulario actual |

> [!IMPORTANT]
> **URL del webhook en `.env`**: N/A para esta feature.

---

## Open Questions

- Ninguna bloqueante. El mapeo ADX eléctrico fue validado con CUPS `ES0022000005446022YT` contra API real.

---

## Proposed Changes

### Database & Models

**Sin cambios.** Rediseño 100 % frontend (spec Assumptions).

---

### Backend — Controllers & Services

**Sin cambios funcionales.**

Archivos existentes que **no se tocan** salvo posible comentario de documentación:

| Archivo | Rol |
|---------|-----|
| `app/Http/Controllers/SipsController.php` | Proxy ADX/AUDAX, devuelve `suministros`, `selectFinal`, `lecturas` |
| `app/Services/Sips/AdxSipsService.php` | Normaliza respuesta ADX (passthrough de `suministros[]`) |
| `app/Services/Sips/SipsProviderRegistry.php` | Resuelve proveedor por `CodCom` |

> Los 47 campos del spec ya llegan en `suministros[0]` desde ADX; el mapeo es responsabilidad del frontend.

---

### Frontend — Configuración y utilidades (nuevos)

#### [NEW] `resources/js/Pages/Eneon/Sips/config/suministroElectricoFields.ts`

Catálogo declarativo de las 5 secciones y 47 campos del spec:

```typescript
export type SuministroFieldFormat = 'text' | 'number' | 'power' | 'date' | 'currency';

export interface SuministroFieldDefinition {
  id: string;           // id DOM único, ej. sips-cups
  label: string;        // Etiqueta español del spec
  keys: string[];       // Atributos API en orden de fallback
  format: SuministroFieldFormat;
  highlight?: boolean;  // true solo para CUPS
}

export interface SuministroSectionDefinition {
  id: string;
  title: string;
  icon: LucideIcon;     // Zap, MapPin, Cpu, User, Calendar...
  fields: SuministroFieldDefinition[];
}

export const SUMINISTRO_ELECTRICO_SECTIONS: SuministroSectionDefinition[] = [ ... ];
```

Incluir los 47 campos exactos del `spec.md` con `keys` verificados contra respuesta ADX real.

#### [NEW] `resources/js/Pages/Eneon/Sips/utils/suministroMappers.ts`

Funciones puras (testeables):

| Función | Responsabilidad |
|---------|-----------------|
| `resolveSuministroValue(suministro, field)` | Primer `keys[]` no vacío |
| `formatSuministroValue(value, format)` | Fecha `DD/MM/AAAA`, potencia `es-ES`, moneda `€` |
| `mapSuministroToViewModel(suministro)` | Proyección completa para las 5 secciones |
| `VALIDATION_FIXTURE_ES0022` | Objeto fixture del CUPS de referencia para tests manuales/automáticos |

**Mapeo crítico verificado (extracto):**

| Label spec | `keys` |
|------------|--------|
| Código distribuidora | `['Cod_Dist']` |
| C.P. suministro | `['Cod_Postal_Suministro', 'CodigoPostal_Suministro']` |
| Telegestionado | `['telegestion', 'Telegestionado_Activo']` |
| Titular del suministro | `['NombreCompleto_Titular', 'Nombre_Titular']` |
| Depósito garantía | `['importeDepositoGarantiaEuros', 'Fianza']` |
| Potencia contratada P1 | `['Pot_Cont_P1']` |
| Derechos de acceso (kW) | `['Der_Acceso_Llano', 'Der_Extension']` |
| Fecha última lectura | `['Fec_Ult_Lect', 'lastlectura']` |
| Fecha caducidad BIE o APM | `['Fec_Lim_Exten', 'fechaLimiteDerechosReconocidos']` |

---

### Frontend — Componentes de suministro (nuevos / reemplazo)

#### [NEW] `resources/js/Pages/Eneon/Sips/components/shared/SipsSuministroDetalle.tsx`

Componente principal que reemplaza la lógica de `AdxSuministroDetails`:

- Props: `suministros: Record<string, unknown>[]`, `tipoServicio: string`
- Si `suministros.length === 0` → `Alert` estado vacío accesible
- Si `suministros.length > 1` → `Alert` informativo «Se muestra el suministro principal; existen N registros adicionales»
- Toma `suministros[0]` y renderiza `SUMINISTRO_ELECTRICO_SECTIONS` con:
  - `SubseccionFormulario` (icono + título)
  - `Row` + `CampoInformativo` en grid responsive (`Col md={6} lg={4}`)
  - CUPS con `highlight` (tipografía mayor, `fw-bold`)
- Solo renderiza si `isElectricityTipo(tipoServicio)` o `isLuzResponse(result)`

#### [DELETE / REPLACE] `resources/js/Pages/Eneon/Sips/components/Adx/AdxSuministroDetails.tsx`

Eliminar tras migrar a `SipsSuministroDetalle`. Actualizar import en `AdxSipsPanel.tsx`.

---

### Frontend — Tabla de consumo y resumen (modificar)

#### [MODIFY] `resources/js/Pages/Eneon/Sips/components/shared/SipsConsumoResults.tsx`

**`SipsResumenLecturas`:**

- Sustituir `border rounded p-3 bg-white` por clases de `Sips.module.css` alineadas a tokens tarjeta Eneon (`--eneon-card-*` si disponibles).
- Añadir `role="group"` + `aria-label` por tarjeta de periodo.
- Mantener lógica de ocultar P4–P6 cuando valor = 0.

**`SipsTablaConsumo`:**

- Envolver en `<div className={styles.tablaConsumosWrapper}>` con scroll horizontal.
- Cabeceras: `scope="col"`, clases `styles.thStickyHeader` (nuevas en `Sips.module.css`, copiadas del patrón `ContratoMultiPuntoRefactored.module.css`).
- Añadir `<caption className="visually-hidden">Historial de consumo SIPS</caption>`.
- Celdas numéricas: `className="text-end"` + `formatSipsNumber`.
- Estado vacío: componente semántico con `role="status"`.
- **Gas**: mismas mejoras visuales/accesibles, columnas actuales sin cambios funcionales.

#### [MODIFY] `resources/js/Pages/Eneon/Sips/Sips.module.css`

Añadir:

```css
/* Secciones suministro */
.seccionSuministro { ... }          /* espaciado entre bloques */
.campoDestacado { ... }             /* CUPS principal */

/* Tabla consumo — patrón Contratos */
.tablaConsumosWrapper { overflow-x: auto; max-height: ...; }
.thStickyHeader { position: sticky; top: 0; z-index: 2; background: ...; }
.tablaConsumos caption { ... }

/* Tarjetas resumen */
.tarjetaResumen { ... }             /* coherente con card-3d / tokens Eneon */
```

---

### Frontend — Paneles y página (modificar)

#### [MODIFY] `resources/js/Pages/Eneon/Sips/components/Adx/AdxSipsPanel.tsx`

| Cambio | Detalle |
|--------|---------|
| Foco accesible | `estilosFoco.scope` → `estilosFoco.contenedorAccesibleContratos` |
| Detalle suministro | `<AdxSuministroDetails>` → `<SipsSuministroDetalle>` |
| Resumen en Datos | **Mantener** bloque `SipsResumenLecturas` en tab `datos-suministros` (decisión usuario) |
| Encabezados | Sustituir `div.bg-light` inline por `styles.seccionTitulo` |
| Metadata | Mantener badge proveedor; opcionalmente integrar `distributor_table` en sección A |

#### [MODIFY] `resources/js/Pages/Eneon/Sips/components/Audax/AudaxSipsPanel.tsx`

| Cambio | Detalle |
|--------|---------|
| Foco accesible | `estilosFoco.contenedorAccesibleContratos` |
| Tab Consumo | Aplicar nuevo estilo tabla vía `SipsConsumoResults` |
| Tab Datos | **Conservar formulario AUDAX actual** (ver «Alcance AUDAX») + estandarizar encabezados/estilos |

### Alcance AUDAX (aclaración)

**Por qué el plan mencionaba «solo mensaje informativo»:** no era eliminar la pantalla AUDAX, sino
que **el catálogo de 47 campos no aplica a AUDAX en esta feature** porque:

1. **ADX** devuelve `suministros[]` con `Cod_Dist`, `Pot_Cont_P1`, `Distribuidora`, etc.
   (`AdxSipsService::normalizeResponse` hace passthrough de ese array).

2. **AUDAX** (`SipsController::consumirSipsAudax`) devuelve solo `selectFinal` + `lecturas`
   tras persistir consumos en BD — **no incluye `suministros[]`** en el JSON al frontend.

3. El tab «Datos Suministros» de AUDAX **ya tiene** un formulario propio (CUPS, titular, provincia,
   optimización, etc.) que hoy **no se rellena** desde la API salvo tarifa/peaje del primer registro
   de consumo.

4. La imagen de referencia y el mapeo validado con `ES0022000005446022YT` corresponden a **datos ADX**.

**Qué sí haremos en AUDAX en esta feature:**

- Mismo look & feel en tab **Consumo** (tabla + resumen rediseñados).
- Foco accesible y encabezados de sección alineados al sistema.
- Formulario Datos Suministros **intacto en funcionalidad** (sin los 47 campos estructurados).

**Qué queda fuera (feature futura si se desea paridad ADX/AUDAX):**

- Mostrar los 47 campos en AUDAX requeriría ampliar backend AUDAX o un nuevo endpoint que exponga
  datos de punto de suministro con la misma estructura que ADX.

#### [MODIFY] `resources/js/Pages/Eneon/Sips/components/SipsUnsupportedMessage.tsx`

Mensaje para comercializadoras sin proveedor SIPS (`resolveSipsProvider` → `null`):

- Título: «Consulta SIPS no disponible»
- Texto: «La comercializadora **{nombre}** no cuenta con capacidad de consulta SIPS.»
- Sin ID interno, códigos `audax`/`adx` ni valores de BD visibles al usuario.
- `role="status"` para lectores de pantalla.

Ya integrado en `Index.tsx` cuando `!provider` tras seleccionar comercializadora.

---

### Frontend — Tipos (modificar)

#### [MODIFY] `resources/js/Pages/Eneon/Sips/types.ts`

Añadir interfaz opcional documentativa (no exhaustiva, solo campos del catálogo):

```typescript
export interface SuministroElectricoAdx {
  CUPS?: string;
  Cod_Dist?: string;
  Distribuidora?: string;
  // ... campos del catálogo spec
  [key: string]: unknown;  // compatibilidad con campos extra no mostrados
}
```

Actualizar `suministros?: SuministroElectricoAdx[]` en `SipsLuzResponse`.

---

### Reutilización de Contratos (imports)

| Componente origen | Uso en SIPS |
|-------------------|-------------|
| `Contratos/UniClienteUniPunto/components/CampoInformativo.tsx` | Campos solo lectura del detalle |
| `Contratos/UniClienteUniPunto/components/SubseccionFormulario.tsx` | Encabezados de sección con icono |
| `Contratos/Shared/estilosFocoAccesible.module.css` | Contenedor foco visible |
| `Shared/Tabs/EneonTabs.tsx` | Ya usado en `SipsPanelTabs` — sin cambios |
| `lucide-react` icons | `Zap`, `MapPin`, `Gauge`, `User`, `CalendarClock` por sección |

---

## Orden de implementación sugerido

1. **Fase 1 — Infraestructura**: `suministroElectricoFields.ts` + `suministroMappers.ts` + tests unitarios de mappers.
2. **Fase 2 — Detalle suministro**: `SipsSuministroDetalle.tsx`, integrar en `AdxSipsPanel`, eliminar `AdxSuministroDetails`.
3. **Fase 3 — Tabla consumo**: estilos CSS + refactor `SipsConsumoResults.tsx` (luz + gas).
4. **Fase 4 — Accesibilidad global**: corregir `estilosFoco` en `Index`, `AudaxSipsPanel`, IDs DOM.
5. **Fase 5 — Validación**: caso CUPS `ES0022000005446022YT`, regresión gas, teclado.

---

## Verification Plan

### Tests automatizados (nuevos)

#### [NEW] `resources/js/Pages/Eneon/Sips/utils/suministroMappers.spec.ts`

> Si el proyecto no tiene runner JS configurado, ejecutar lógica vía script temporal o añadir
> Vitest como devDependency mínima. Alternativa: fixture PHP con assert en test de integración frontend.

Casos mínimos:

| Test | Assert |
|------|--------|
| `resolveSuministroValue` fallback CP | `Cod_Postal_Suministro` vacío → usa `CodigoPostal_Suministro` |
| `formatSuministroValue` fecha | `2024-04-17T00:00:00+0000` → `17/04/2024` |
| `formatSuministroValue` potencia | `5.75` → `5,75` (locale es-ES) |
| `mapSuministroToViewModel` fixture | ≥20 de 22 valores del caso de validación spec |
| Campos vacíos | `null`, `''`, `undefined` → `—` |

### Tests existentes — regresión

```bash
php artisan test --filter=Sips
```

Verificar que integraciones SIPS en contratos (`useCupsElectrico`, `CupsElectricSection`) no se ven afectadas (no modifican archivos de contratos).

### Verificación manual en navegador

**Precondición**: Comercializadora ADX configurada, `.env` ADX SIPS operativo.

| # | Paso | Resultado esperado |
|---|------|-------------------|
| 1 | Ir a `/sips`, seleccionar comercializadora ADX | Panel ADX visible, foco visible al tabular |
| 2 | Consultar `ES0022000005446022YT`, tipo Electricity | Consulta exitosa |
| 3 | Tab «Datos Suministros» | 5 secciones con iconos, 47 etiquetas español, sin claves API |
| 4 | Verificar valores control | CUPS, 0022, UNION FENOSA, 2.0TD, Guadalajara, P1/P2 5,75 kW |
| 5 | Campos vacíos | Muestran `—` |
| 6 | Tab «Consumo» | Resumen + tabla con `scope`, scroll, sticky header |
| 7 | Tabular tabla consumo | Cabeceras permanecen visibles al scroll |
| 8 | Consultar CUPS gas (ej. existente en entorno) | Tabla gas con columnas actuales, nuevo estilo |
| 9 | AUDAX comercializadora | Formulario AUDAX intacto; mensaje informativo si sin suministros |
| 10 | Solo teclado | Orden: comercializadora → CUPS → tipo → Consultar → tabs → contenido |

### Build

```bash
npm run build
```

Sin errores TypeScript ni regresiones de bundle.

---

## Archivos afectados — resumen

| Acción | Archivo |
|--------|---------|
| NEW | `Sips/config/suministroElectricoFields.ts` |
| NEW | `Sips/utils/suministroMappers.ts` |
| NEW | `Sips/utils/suministroMappers.spec.ts` (o equivalente) |
| NEW | `Sips/components/shared/SipsSuministroDetalle.tsx` |
| MODIFY | `Sips/components/shared/SipsConsumoResults.tsx` |
| MODIFY | `Sips/components/Adx/AdxSipsPanel.tsx` |
| MODIFY | `Sips/components/Audax/AudaxSipsPanel.tsx` |
| MODIFY | `Sips/Index.tsx` |
| MODIFY | `Sips/Sips.module.css` |
| MODIFY | `Sips/types.ts` |
| DELETE | `Sips/components/Adx/AdxSuministroDetails.tsx` |

**Sin tocar**: `SipsController.php`, `AdxSipsService.php`, `useSipsConsulta.ts`, hooks de Contratos.

---

## Riesgos y mitigaciones

| Riesgo | Mitigación |
|--------|------------|
| Acoplamiento a componentes UniClienteUniPunto | Import directo documentado; refactor futuro a Shared |
| Campos API ausentes (`Suministro_Contratable`, etc.) | Fallback `—` ya en spec |
| Tabla con 39 lecturas (CUPS referencia) | `max-height` + scroll vertical + sticky header |
| `CampoInformativo` con `tabIndex={-1}` | Aceptable para solo lectura; labels asociados vía `htmlFor` |

---

## Criterios de done

- [ ] 47 campos visibles con etiquetas español en consulta eléctrica ADX
- [ ] Caso validación `ES0022000005446022YT` ≥20/22 valores correctos
- [ ] Cero claves API visibles como etiquetas
- [ ] Tabla consumo con `scope`, sticky header, scroll responsive
- [ ] `estilosFoco.contenedorAccesibleContratos` en toda la página SIPS
- [ ] Gas sin regresión de columnas
- [ ] `npm run build` exitoso
