# Diseño: Sección 16.2 — N° de Vanos Abiertos + Simplificación de Imagen

**Fecha:** 2026-05-07  
**Autor:** Opencode (diseño asistido por IA)  
**Estado:** Aprobado por usuario  

---

## 1. Resumen Ejecutivo

Se implementa la tabla **"N° de Vanos Abiertos"** en la sección 16.2 (Descripción Volumétrica) de las fichas de inventario, permitiendo registrar cuántas puertas y ventanas existen por planta (PB, 1P, 2P, Otros P). Además, se simplifica la carga de la imagen "Esquema de ubicación" para que sea únicamente subir/quitar la imagen, sin metadatos adicionales (alineado con el comportamiento de la sección 6).

---

## 2. Alcance

### Incluido
1. Campo `vanos_abiertos` (JSON) en tabla `f_inv_seccion_16`
2. Componente React `VanosAbiertos.tsx` con tabla de inputs numéricos
3. Integración en `Section162.tsx`
4. Validaciones backend en `SaveSeccion16Action`
5. Simplificación de UI de carga de imagen en `medios-section.tsx`
6. Tests de guardado

### Excluido
- Cambios en sección 16.1 (Sistema Constructivo) — ya implementada
- Cambios en otras secciones

---

## 3. Arquitectura de Datos

### 3.1 Estructura JSON

```typescript
interface VanosAbiertosData {
  puertas: {
    pb: number | null;
    p1: number | null;
    p2: number | null;
    otros: number | null;
  };
  ventanas: {
    pb: number | null;
    p1: number | null;
    p2: number | null;
    otros: number | null;
  };
}
```

Valores: enteros ≥ 0 o `null` (vacío). `null` representa "no ingresado" (guardado parcial permitido).

### 3.2 Base de Datos

**Tabla:** `f_inv_seccion_16`  
**Nueva columna:** `vanos_abiertos` (TEXT, nullable)

Ubicación: después de `espacios_complementarios`.

```php
$table->text('vanos_abiertos')->nullable()->after('espacios_complementarios');
```

> **Nota:** Según reglas del proyecto, se edita la migración existente (no se crea nueva migración) porque no hay datos en producción.

### 3.3 Modelo

**Archivo:** `app/Models/InvSeccion16.php`  
**Cambio:** Agregar cast:

```php
protected $casts = [
    'crujias_config' => 'array',
    'vanos_abiertos' => 'array',
];
```

---

## 4. Backend

### 4.1 Action: SaveSeccion16Action

**Archivo:** `app/Actions/FichaInventario/SaveSeccion16Action.php`

**Cambios:**
1. Agregar `vanos_abiertos` a `FIELDS`
2. Agregar reglas de validación:

```php
'vanos_abiertos' => ['nullable', 'array'],
'vanos_abiertos.puertas' => ['nullable', 'array'],
'vanos_abiertos.puertas.pb' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.puertas.p1' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.puertas.p2' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.puertas.otros' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.ventanas' => ['nullable', 'array'],
'vanos_abiertos.ventanas.pb' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.ventanas.p1' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.ventanas.p2' => ['nullable', 'integer', 'min:0'],
'vanos_abiertos.ventanas.otros' => ['nullable', 'integer', 'min:0'],
```

3. Guardar como JSON string (mismo patrón que `descripcion_volumetrica`)

### 4.2 Controller: FichaInventarioController

**Archivo:** `app/Http/Controllers/FichaInventarioController.php`

**Cambio:** Incluir `vanos_abiertos` en la respuesta de la sección 16 (igual que `descripcion_volumetrica` y `espacios_complementarios`).

---

## 5. Frontend

### 5.1 Tipos

**Archivo:** `resources/js/pages/fichas-inventario/components/sections/seccion16/types.ts`

**Nuevas interfaces:**

```typescript
export interface VanosPlantasData {
  pb: number | null;
  p1: number | null;
  p2: number | null;
  otros: number | null;
}

export interface VanosAbiertosData {
  puertas: VanosPlantasData;
  ventanas: VanosPlantasData;
}
```

**Valor por defecto:**

```typescript
export const defaultVanosAbiertos: VanosAbiertosData = {
  puertas: { pb: null, p1: null, p2: null, otros: null },
  ventanas: { pb: null, p1: null, p2: null, otros: null },
};
```

### 5.2 Componente: VanosAbiertos.tsx

**Archivo nuevo:** `resources/js/pages/fichas-inventario/components/sections/seccion16/VanosAbiertos.tsx`

**Responsabilidad:** Renderizar tabla de 2 filas × 4 columnas con inputs numéricos.

**Props:**
```typescript
interface VanosAbiertosProps {
  data: VanosAbiertosData;
  onChange: (data: VanosAbiertosData) => void;
}
```

**Comportamiento:**
- Cada celda es un `<Input type="number" min="0" />` con ancho fijo (~60px)
- Valor vacío = `null`
- Cambio en cualquier celda actualiza el objeto completo y llama `onChange`

### 5.3 Integración en Section162.tsx

**Archivo:** `resources/js/pages/fichas-inventario/components/sections/Section162.tsx`

**Cambios:**
1. Agregar `vanos_abiertos` al estado inicial de `useSectionForm`
2. Parsear `vanos_abiertos` con `parseJsonField` (igual que `descripcion_volumetrica`)
3. Renderizar `<VanosAbiertos />` entre `DescripcionVolumetrica` y `MediosSection`
4. Incluir `vanos_abiertos` en el guardado (ya manejado por `useSectionForm`)

### 5.4 Simplificación de Imagen

**Archivo:** `resources/js/pages/fichas-inventario/components/sections/medios-section.tsx`

**Cambio:** Agregar `'16': { label: 'esquema' }` al objeto `singleFileSections` (línea ~314-317).

**Resultado:** La sección 16 usará `InlinePreviewDropzone` en lugar de la vista completa con metadatos.

**Antes:**
```typescript
const singleFileSections: Record<string, { label: string }> = {
    '6': { label: 'plano' },
    '7': { label: 'fotografía' },
};
```

**Después:**
```typescript
const singleFileSections: Record<string, { label: string }> = {
    '6': { label: 'plano' },
    '7': { label: 'fotografía' },
    '16': { label: 'esquema' },
};
```

---

## 6. Diseño Visual

```
┌─────────────────────────────────────────────────────────────┐
│  16.2 DESCRIPCIÓN VOLUMÉTRICA                                │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  [Grid de ~70 checkboxes existente...]                     │
│                                                              │
│  ═══════════════════════════════════════════════════════    │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │           N° DE VANOS ABIERTOS                        │   │
│  ├─────────────┬─────┬─────┬─────┬─────────────────────┤   │
│  │             │ PB  │ 1P  │ 2P  │     Otros P         │   │
│  ├─────────────┼─────┼─────┼─────┼─────────────────────┤   │
│  │  PUERTAS    │ [2] │ [1] │ [0] │        [ ]          │   │
│  ├─────────────┼─────┼─────┼─────┼─────────────────────┤   │
│  │  VENTANA    │ [4] │ [3] │ [2] │        [1]          │   │
│  └─────────────┴─────┴─────┴─────┴─────────────────────┘   │
│                                                              │
│  Inputs: tipo número, ancho ~60px, placeholder vacío        │
│                                                              │
│  ═══════════════════════════════════════════════════════    │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  16.2. Esquema de ubicación                           │   │
│  │  ┌────────────────────────────────────────────┐     │   │
│  │  │         [Preview de imagen subida]          │     │   │
│  │  │        [Reemplazar]  [Quitar]              │     │   │
│  │  └────────────────────────────────────────────┘     │   │
│  │  O si no hay imagen:                                  │   │
│  │  ┌────────────────────────────────────────────┐     │   │
│  │  │     📤 Subir esquema                        │     │   │
│  │  │     [Seleccionar imagen]                    │     │   │
│  │  └────────────────────────────────────────────┘     │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘
```

---

## 7. Tests

### 7.1 Test: SaveSeccion16Test

**Archivo:** `tests/Feature/SaveSeccion16Test.php`

**Escenarios a cubrir:**
1. Guardar `vanos_abiertos` con valores válidos (enteros ≥ 0)
2. Guardar `vanos_abiertos` con valores null (guardado parcial)
3. Validación rechaza valores negativos
4. Validación rechaza strings en campos numéricos
5. La imagen sigue funcionando correctamente

---

## 8. Archivos Afectados

| # | Archivo | Tipo de cambio |
|---|---------|----------------|
| 1 | `database/migrations/...create_f_inv_seccion_16_table.php` | Editar — agregar columna |
| 2 | `app/Models/InvSeccion16.php` | Editar — agregar cast |
| 3 | `app/Actions/FichaInventario/SaveSeccion16Action.php` | Editar — validación y guardado |
| 4 | `app/Http/Controllers/FichaInventarioController.php` | Editar — respuesta |
| 5 | `resources/js/.../seccion16/types.ts` | Editar — nuevas interfaces |
| 6 | `resources/js/.../seccion16/VanosAbiertos.tsx` | **Crear** — nuevo componente |
| 7 | `resources/js/.../sections/Section162.tsx` | Editar — integrar componente |
| 8 | `resources/js/.../sections/medios-section.tsx` | Editar — simplificar imagen |
| 9 | `tests/Feature/SaveSeccion16Test.php` | Editar — tests nuevos |

---

## 9. Notas de Implementación

- **No crear nueva migración** — editar la existente según reglas del proyecto
- **Seguir patrones existentes** — `parseJsonField`, `useSectionForm`, validaciones del Action
- **Guardado parcial** — todos los campos nullable para permitir borradores
- **Imagen** — el cambio en `singleFileSections` es suficiente; no requiere cambios en el Action ni en el serializer porque la lógica de archivos ya está abstraída en `MediosSection`

---

## 10. Criterios de Aceptación

- [ ] Se visualiza la tabla "N° de Vanos Abiertos" con 2 filas y 4 columnas
- [ ] Los inputs aceptan solo números enteros ≥ 0
- [ ] Se guarda correctamente en la base de datos como JSON
- [ ] Se carga correctamente al editar una ficha existente
- [ ] La imagen "Esquema de ubicación" se sube/quita sin metadatos
- [ ] Todos los tests existentes siguen pasando
- [ ] Los nuevos tests de `vanos_abiertos` pasan
