# Análisis y Mejoras: Vista de Detalle del Paciente

## 📊 Análisis Actual

### Estructura Existente:
```
├── Sidebar (col-4)
│   ├── Card de Paciente
│   ├── Información Personal
│   ├── Datos Fiscales
│   └── Botones de Acción
│
└── Content (col-8)
    ├── Tabs
    │   ├── Consultas (prescriptions)
    │   └── Compras (purchases)
    └── Tab Content
```

### ✅ Aspectos Positivos:
- ✅ **Ya existe offcanvas** para múltiples ventas por prescripción
- ✅ Timeline de prescripciones ordenado cronológicamente
- ✅ Edición de prescription inline
- ✅ Info de doctor y consulta accesible

### ⚠️ Puntos a Mejorar:

1. **Duplicación de información**
   - "Ciudad" aparece 2 veces (líneas 73 y 61)
   - Información fiscal colapsada (poco visible)

2. **Jerarquía visual**
   - Prescription debería ser más prominente
   - Ventas asociadas poco visibles

3. **UX de múltiples ventas**
   - Offcanvas funciona, pero podría ser un accordion inline

4. **Métricas ausentes**
   - Sin indicador de antigüedad de graduación
   - Sin advertencia si graduación > 2 años

---

## 🎨 Mejoras Propuestas

### 1. **Reorganizar Sidebar - Información Más Limpia**

**Cambios:**
- ❌ Eliminar duplicación de "Ciudad"
- ✅ Agregar badges de estado (prescripciones activas, ventas pendientes)
- ✅ Teléfonos más compactos
- ✅ Botón de fusión de pacientes

**Antes:**
```html
<li class="mb-2">Ciudad:</li>
<li class="mb-2">Ciudad:</li>  <!-- Duplicado -->
```

**Después:**
```html
<li class="mb-2">
    <span class="fw-medium">Dirección Completa:</span><br>
    <small class="text-muted">
        <?=$patient->street?><br>
        <?=$patient->city?>, <?=$patient->state?> CP: <?=$patient->postal_code?>
    </small>
</li>
```

### 2. **Timeline de Prescripciones Mejorado**

**Visual propuesto:**

```html
<div class="prescription-card">
    <!-- HEADER con indicadores visuales -->
    <div class="card-header">
        <div class="d-flex justify-content-between">
            <div>
                <h6>📋 Prescripción #<?=$prescription->id?></h6>
                <small class="text-muted">
                    <?=$prescription->created_at->toLocalizedString('d MMM yyyy')?>
                    <span class="badge bg-label-<?= $ageColor ?>">
                        <?=$prescription->created_at->humanize()?>
                    </span>
                </small>
            </div>
            <div class="prescription-stats">
                <!-- Badge de antigüedad -->
                <?php
                $months = $prescription->created_at->diffInMonths(now());
                $ageColor = $months > 24 ? 'danger' : ($months > 12 ? 'warning' : 'success');
                ?>
                <span class="badge bg-<?=$ageColor?> me-2">
                    <?=$months?> meses
                </span>

                <!-- Badge de ventas -->
                <?php $salesCount = count($prescription->sales ?? []); ?>
                <span class="badge bg-label-info">
                    <i class="mdi mdi-cart"></i> <?=$salesCount?> venta(s)
                </span>
            </div>
        </div>
    </div>

    <!-- BODY - Graduación -->
    <div class="card-body">
        <!-- Tabla de graduación existente -->
        ...

        <!-- SECCIÓN DE VENTAS INLINE (en vez de offcanvas) -->
        <?php if ($salesCount > 1): ?>
            <div class="accordion mt-3" id="accordion-sales-<?=$prescription->id?>">
                <div class="accordion-item">
                    <h2 class="accordion-header">
                        <button class="accordion-button collapsed" type="button"
                                data-bs-toggle="collapse"
                                data-bs-target="#sales-collapse-<?=$prescription->id?>">
                            <i class="mdi mdi-cart-outline me-2"></i>
                            Ver <?=$salesCount?> Órdenes Asociadas
                        </button>
                    </h2>
                    <div id="sales-collapse-<?=$prescription->id?>"
                         class="accordion-collapse collapse"
                         data-bs-parent="#accordion-sales-<?=$prescription->id?>">
                        <div class="accordion-body">
                            <!-- Lista de ventas -->
                            <?php foreach ($prescription->sales as $sale): ?>
                                <div class="sale-item border-bottom pb-2 mb-2">
                                    <div class="d-flex justify-content-between">
                                        <div>
                                            <strong>Venta #<?=$sale->id?></strong>
                                            <br>
                                            <small class="text-muted">
                                                <?=$sale->created_at->format('d/m/Y')?>
                                            </small>
                                        </div>
                                        <div class="text-end">
                                            <div class="mb-1">
                                                <span class="badge bg-label-<?=$sale->delivery === 'delivered' ? 'success' : 'warning'?>">
                                                    <?=ucfirst($sale->delivery)?>
                                                </span>
                                            </div>
                                            <div>
                                                <a href="/venta/<?=$sale->id?>" class="btn btn-xs btn-primary">
                                                    <i class="mdi mdi-eye"></i>
                                                </a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            <?php endforeach; ?>

                            <a href="/nueva-orden/venta/-/paciente/<?=$prescription->patient?>/prescription/<?=$prescription->id?>"
                               class="btn btn-sm btn-primary w-100 mt-2">
                                <i class="mdi mdi-plus"></i> Nueva Orden
                            </a>
                        </div>
                    </div>
                </div>
            </div>
        <?php endif; ?>
    </div>
</div>
```

### 3. **Dashboard de Métricas (Nuevo Tab)**

Agregar un tab "Resumen" antes de "Consultas":

```html
<li class="nav-item">
    <button type="button" class="nav-link active" role="tab"
            data-bs-toggle="tab"
            data-bs-target="#navs-justified-summary">
        <i class="mdi mdi-chart-box me-2"></i> Resumen
    </button>
</li>
```

**Contenido del tab:**

```html
<div class="tab-pane fade show active" id="navs-justified-summary">
    <div class="row">
        <!-- Métrica 1: Prescripciones -->
        <div class="col-md-3">
            <div class="card">
                <div class="card-body text-center">
                    <div class="avatar avatar-lg mx-auto mb-2">
                        <span class="avatar-initial rounded-circle bg-label-primary">
                            <i class="mdi mdi-file-document mdi-24px"></i>
                        </span>
                    </div>
                    <h4 class="mb-0"><?=count($prescriptions)?></h4>
                    <small class="text-muted">Prescripciones</small>
                </div>
            </div>
        </div>

        <!-- Métrica 2: Última Graduación -->
        <div class="col-md-3">
            <div class="card">
                <div class="card-body text-center">
                    <div class="avatar avatar-lg mx-auto mb-2">
                        <span class="avatar-initial rounded-circle bg-label-<?=$lastPrescriptionColor?>">
                            <i class="mdi mdi-calendar-clock mdi-24px"></i>
                        </span>
                    </div>
                    <h4 class="mb-0">
                        <?php if (!empty($prescriptions)):
                            echo $prescriptions[0]->created_at->diffInMonths(now());
                        else:
                            echo 'N/A';
                        endif; ?>
                    </h4>
                    <small class="text-muted">Meses desde última</small>
                </div>
            </div>
        </div>

        <!-- Métrica 3: Total de Ventas -->
        <div class="col-md-3">
            <div class="card">
                <div class="card-body text-center">
                    <div class="avatar avatar-lg mx-auto mb-2">
                        <span class="avatar-initial rounded-circle bg-label-success">
                            <i class="mdi mdi-cart mdi-24px"></i>
                        </span>
                    </div>
                    <h4 class="mb-0">
                        <?php
                        $totalSales = 0;
                        foreach ($prescriptions as $p) {
                            $totalSales += count($p->sales ?? []);
                        }
                        echo $totalSales;
                        ?>
                    </h4>
                    <small class="text-muted">Órdenes Totales</small>
                </div>
            </div>
        </div>

        <!-- Métrica 4: Pendiente de Entrega -->
        <div class="col-md-3">
            <div class="card">
                <div class="card-body text-center">
                    <div class="avatar avatar-lg mx-auto mb-2">
                        <span class="avatar-initial rounded-circle bg-label-warning">
                            <i class="mdi mdi-clock-alert mdi-24px"></i>
                        </span>
                    </div>
                    <h4 class="mb-0">
                        <?php
                        $pending = 0;
                        foreach ($prescriptions as $p) {
                            foreach ($p->sales ?? [] as $sale) {
                                if ($sale->delivery !== 'delivered') $pending++;
                            }
                        }
                        echo $pending;
                        ?>
                    </h4>
                    <small class="text-muted">Pendientes</small>
                </div>
            </div>
        </div>
    </div>

    <!-- Alerta si graduación antigua -->
    <?php if (!empty($prescriptions) && $prescriptions[0]->created_at->diffInMonths(now()) > 24): ?>
        <div class="alert alert-warning mt-3" role="alert">
            <h5 class="alert-heading">⚠️ Graduación Antigua</h5>
            <p class="mb-0">
                La última graduación fue hace <strong><?=$prescriptions[0]->created_at->diffInMonths(now())?> meses</strong>.
                Se recomienda una nueva evaluación visual.
            </p>
        </div>
    <?php endif; ?>

    <!-- Gráfica de Timeline (opcional) -->
    <div class="card mt-3">
        <div class="card-header">
            <h5 class="card-title mb-0">📈 Historial de Graduaciones</h5>
        </div>
        <div class="card-body">
            <canvas id="prescriptionTimeline"></canvas>
        </div>
    </div>
</div>
```

### 4. **Botones de Acción en Sidebar**

**Agregar botón de fusión:**

```html
<div class="d-flex justify-content-center gap-2">
    <!-- Botones existentes -->
    <a href="javascript:;" ... ><i class="mdi mdi-pencil"></i></a>
    <a href="javascript:;" ... ><i class="mdi mdi-phone-plus"></i></a>
    <a href="javascript:;" ... ><i class="mdi mdi-file-document-edit"></i></a>
    <a href="/nueva-consulta/<?=$patient->id?>" ... ><i class="mdi mdi-prescription"></i></a>
    <a href="javascript:;" ... ><i class="mdi mdi-eye-plus-outline"></i></a>

    <!-- NUEVO: Botón de fusión -->
    <button type="button"
            class="btn btn-sm btn-warning"
            onclick="PatientMergeUtility.openMerge(<?=$patient->id?>)"
            title="Fusionar paciente">
        <i class="mdi mdi-account-convert"></i>
    </button>
</div>
```

### 5. **Indicadores Visuales de Antigüedad**

**Helper PHP para calcular color:**

```php
<?php
function getPrescriptionAgeColor($createdAt) {
    $months = $createdAt->diffInMonths(now());

    if ($months <= 12) return 'success';  // Verde: Fresh
    if ($months <= 24) return 'warning';  // Amarillo: Revisar pronto
    return 'danger';                       // Rojo: Urgente
}

function getPrescriptionAgeText($createdAt) {
    $months = $createdAt->diffInMonths(now());

    if ($months <= 12) return '✓ Vigente';
    if ($months <= 24) return '⚠ Revisar pronto';
    return '⚠ Graduación antigua';
}
?>
```

---

## 🧪 Tests Unitarios para Sistema de Merge

### Archivo: `tests/unit/Services/PatientMergeServiceTest.php`

```php
<?php

namespace Tests\Unit\Services;

use App\Services\PatientMergeService;
use App\Models\PatientModel;
use App\Models\PrescriptionModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;

class PatientMergeServiceTest extends CIUnitTestCase
{
    use DatabaseTestTrait;

    protected $refresh = true;
    protected $namespace = 'App';

    protected PatientMergeService $service;

    protected function setUp(): void
    {
        parent::setUp();
        $this->service = new PatientMergeService();
    }

    /**
     * Test: Búsqueda de duplicados por SOUNDEX
     */
    public function testFindDuplicatesBySoundex()
    {
        // Crear pacientes de prueba
        $this->createTestPatients();

        // Buscar duplicados de "Juan"
        $duplicates = $this->service->findDuplicates('Juan', 'Pérez');

        $this->assertIsArray($duplicates);
        $this->assertGreaterThan(0, count($duplicates));
        $this->assertArrayHasKey('similarity_score', $duplicates[0]);
    }

    /**
     * Test: No encontrar duplicados cuando no existen
     */
    public function testFindDuplicatesReturnsEmptyWhenNone()
    {
        $duplicates = $this->service->findDuplicates('XYZNameThatDoesNotExist', 'ZYXLastName');

        $this->assertIsArray($duplicates);
        $this->assertCount(0, $duplicates);
    }

    /**
     * Test: Merge exitoso migra todos los registros
     */
    public function testMergeSuccessfullyMigratesRecords()
    {
        // Crear 2 pacientes
        $sourceId = $this->createPatientWithRecords();
        $targetId = $this->createPatientWithRecords();

        // Obtener counts antes
        $sourcePrescriptionsBefore = model(PrescriptionModel::class)
            ->where('patient', $sourceId)
            ->countAllResults();

        // Ejecutar merge
        $result = $this->service->merge($sourceId, $targetId);

        $this->assertTrue($result);

        // Verificar que prescripciones fueron migradas
        $targetPrescriptionsAfter = model(PrescriptionModel::class)
            ->where('patient', $targetId)
            ->countAllResults();

        $this->assertGreaterThan(0, $targetPrescriptionsAfter);

        // Verificar que paciente origen fue eliminado (soft delete)
        $sourcePatient = model(PatientModel::class)->withDeleted()->find($sourceId);
        $this->assertNotNull($sourcePatient->deleted_at);
    }

    /**
     * Test: Merge falla si pacientes son el mismo
     */
    public function testMergeFailsWhenSamePatient()
    {
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('No se puede fusionar un paciente consigo mismo');

        $patientId = $this->createPatientWithRecords();
        $this->service->merge($patientId, $patientId);
    }

    /**
     * Test: Merge falla si paciente origen no existe
     */
    public function testMergeFailsWhenSourceNotExists()
    {
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Uno o ambos pacientes no existen');

        $targetId = $this->createPatientWithRecords();
        $this->service->merge(99999, $targetId); // ID que no existe
    }

    /**
     * Test: Get patient stats retorna estructura correcta
     */
    public function testGetPatientStatsReturnsCorrectStructure()
    {
        $patientId = $this->createPatientWithRecords();

        $stats = $this->service->getPatientStats($patientId);

        $this->assertIsArray($stats);
        $this->assertArrayHasKey('prescriptions', $stats);
        $this->assertArrayHasKey('consultations', $stats);
        $this->assertArrayHasKey('sales', $stats);
        $this->assertArrayHasKey('orders', $stats);
    }

    /**
     * Test: Similitud calculada correctamente
     */
    public function testSimilarityScoreCalculation()
    {
        $this->createTestPatients();

        $duplicates = $this->service->findDuplicates('Juan', 'Pérez');

        foreach ($duplicates as $duplicate) {
            $this->assertIsFloat($duplicate['similarity_score']);
            $this->assertGreaterThanOrEqual(0, $duplicate['similarity_score']);
            $this->assertLessThanOrEqual(100, $duplicate['similarity_score']);
        }
    }

    /**
     * Test: Exclude ID funciona correctamente
     */
    public function testFindDuplicatesExcludesGivenId()
    {
        $excludeId = $this->createPatientWithRecords();

        $duplicates = $this->service->findDuplicates('Test', 'Patient', $excludeId);

        foreach ($duplicates as $duplicate) {
            $this->assertNotEquals($excludeId, $duplicate['id']);
        }
    }

    // ======= Helpers =======

    private function createTestPatients()
    {
        // Crear personas y pacientes de prueba
        $personData = [
            ['name' => 'Juan', 'last_name' => 'Pérez'],
            ['name' => 'Juana', 'last_name' => 'Perez'],
            ['name' => 'José', 'last_name' => 'Pérez'],
        ];

        foreach ($personData as $data) {
            $personId = model(\App\Models\PersonModel::class)->insert($data);
            model(PatientModel::class)->insert([
                'person' => $personId,
                'store' => 1
            ]);
        }
    }

    private function createPatientWithRecords(): int
    {
        $personId = model(\App\Models\PersonModel::class)->insert([
            'name' => 'Test',
            'last_name' => 'Patient'
        ]);

        $patientId = model(PatientModel::class)->insert([
            'person' => $personId,
            'store' => 1
        ]);

        // Crear prescription
        model(PrescriptionModel::class)->insert([
            'patient' => $patientId
        ]);

        return $patientId;
    }
}
```

### Archivo: `tests/integration/MergeFlowTest.php`

```php
<?php

namespace Tests\Integration;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\DatabaseTestTrait;

class MergeFlowTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    use DatabaseTestTrait;

    protected $refresh = true;

    /**
     * Test: Flujo completo de búsqueda y merge vía API
     */
    public function testCompleteMergeFlow()
    {
        // 1. Crear pacientes duplicados
        $sourceId = $this->createPatient('Juan', 'Pérez');
        $targetId = $this->createPatient('Juan', 'Perez');

        // 2. Buscar duplicados vía API
        $response = $this->withSession(['store' => (object)['id' => 1]])
            ->post('/patient/search-duplicates', [
                'name' => 'Juan',
                'last_name' => 'Pérez'
            ]);

        $response->assertStatus(200);
        $response->assertJSONFragment(['found' => true]);

        // 3. Ejecutar merge vía API
        $mergeResponse = $this->withSession(['store' => (object)['id' => 1]])
            ->post('/patient/merge', [
                'source_id' => $sourceId,
                'target_id' => $targetId
            ]);

        $mergeResponse->assertStatus(200);
        $mergeResponse->assertJSONFragment(['success' => true]);

        // 4. Verificar que source fue eliminado
        $sourcePatient = model(\App\Models\PatientModel::class)
            ->withDeleted()
            ->find($sourceId);

        $this->assertNotNull($sourcePatient->deleted_at);
    }

    private function createPatient($name, $lastName): int
    {
        $personId = model(\App\Models\PersonModel::class)->insert([
            'name' => $name,
            'last_name' => $lastName
        ]);

        return model(\App\Models\PatientModel::class)->insert([
            'person' => $personId,
            'store' => 1
        ]);
    }
}
```

---

## ✅ Checklist de Verificación del Sistema

### Backend
- [ ] `PatientMergeService::findDuplicates()` funciona correctamente
- [ ] `PatientMergeService::merge()` migra todos los registros
- [ ] `PatientMergeService::getPatientStats()` retorna datos correctos
- [ ] Rutas están en el orden correcto (antes del resource)
- [ ] Transacciones DB se rollback en caso de error
- [ ] Logs se generan correctamente

### Frontend
- [ ] Modal de duplicados se abre correctamente
- [ ] Modal se resetea al cerrar
- [ ] Búsqueda de duplicados funciona
- [ ] Búsqueda de target funciona
- [ ] Preview de merge muestra información correcta
- [ ] Merge ejecuta y redirige correctamente
- [ ] Stats se cargan correctamente

### Vistas
- [ ] Vista de detalle muestra prescripciones correctamente
- [ ] Múltiples ventas se muestran (accordion u offcanvas)
- [ ] Botón de fusión está visible
- [ ] Métricas se calculan correctamente
- [ ] Indicadores de antigüedad muestran colores correctos

---

## 🚀 Comandos para Ejecutar Tests

```bash
# Ejecutar todos los tests
vendor/bin/phpunit

# Ejecutar solo tests de merge
vendor/bin/phpunit tests/unit/Services/PatientMergeServiceTest.php

# Ejecutar con coverage
vendor/bin/phpunit --coverage-html coverage/
```

---

## 📝 Resumen de Cambios Recomendados

### Alta Prioridad:
1. ✅ Eliminar duplicación de "Ciudad" en sidebar
2. ✅ Agregar badges de antigüedad en prescripciones
3. ✅ Cambiar offcanvas por accordion inline para ventas múltiples
4. ✅ Agregar botón de fusión en sidebar

### Media Prioridad:
5. ⚠ Agregar tab de "Resumen" con métricas
6. ⚠ Implementar alertas de graduación antigua
7. ⚠ Crear tests unitarios

### Baja Prioridad:
8. 📊 Agregar gráfica de timeline (opcional)
9. 📊 Dashboard de métricas avanzado

