<?php
require_once __DIR__ . '/config.php';

requireLicense();
$method = $_SERVER['REQUEST_METHOD'];
$action = strParam('action');

// ─── ACCIONES PÚBLICAS (sin autenticación requerida) ─────────────────────────
// module_perms GET: necesario para cargar permisos antes/después del login
if ($action === 'module_perms' && $method === 'GET') {
    try {
        $db = getDB();
        $rows = $db->query("SELECT role, perms FROM module_perms")->fetchAll();
        $result = [];
        foreach ($rows as $row) {
            $result[$row['role']] = json_decode($row['perms'], true) ?? [];
        }
        $lockedSt = $db->prepare("SELECT val FROM config WHERE `key`='module_locked_off' LIMIT 1");
        $lockedSt->execute();
        $lockedRow = $lockedSt->fetch();
        $lockedOff = $lockedRow ? json_decode($lockedRow['val'], true) : [];
        jsonOk(['perms' => $result, 'lockedOff' => $lockedOff]);
    } catch (Throwable $e) {
        jsonOk(['perms' => [], 'lockedOff' => []]);
    }
}

$user   = requireAuth();
$db     = getDB();

// ─── EMPRESA: GET / UPDATE ────────────────────────────────────────────────────
if ($action === 'company') {
    if ($method === 'GET') {
        $st = $db->query("SELECT * FROM company LIMIT 1");
        jsonOk($st->fetch() ?: []);
    }
    if ($method === 'PUT') {
        requirePerm($user, 'config');
        $d = body();
        unset($d['id'], $d['updated_at']);
        $cols = ['name','logo','address','phone','email','currency','currency_code','ai_api_key'];
        $filtered = array_intersect_key($d, array_flip($cols));
        if ($filtered) {
            $set  = implode(',', array_map(fn($c) => "`$c`=?", array_keys($filtered)));
            $vals = array_values($filtered);
            // Si no hay fila, insertar
            $cnt = (int)$db->query("SELECT COUNT(*) FROM company")->fetchColumn();
            if ($cnt === 0) {
                $db->prepare("INSERT INTO company (name) VALUES (?)")->execute(['Podología Más Bela']);
            }
            $db->prepare("UPDATE company SET $set LIMIT 1")->execute($vals);
        }
        $st = $db->query("SELECT * FROM company LIMIT 1");
        jsonOk($st->fetch());
    }
}

// ─── DASHBOARD: ESTADÍSTICAS ──────────────────────────────────────────────────
if ($action === 'dashboard' && $method === 'GET') {
    requirePerm($user, 'dashboard');
    $today = date('Y-m-d');
    $month = date('Y-m');

    // Atenciones hoy
    $attHoy = (int)$db->prepare("SELECT COUNT(*) FROM attendances WHERE date=?")->execute([$today]) && true
              ? (int)$db->query("SELECT COUNT(*) FROM attendances WHERE date='$today'")->fetchColumn()
              : 0;
    $stAH = $db->prepare("SELECT COUNT(*) FROM attendances WHERE date=?");
    $stAH->execute([$today]); $attHoy = (int)$stAH->fetchColumn();

    // Ingresos hoy (pagados)
    $stIH = $db->prepare("SELECT COALESCE(SUM(price),0) FROM attendances WHERE date=? AND pay_status='pagado'");
    $stIH->execute([$today]); $ingresosHoy = (float)$stIH->fetchColumn();

    // Por cobrar (pendientes hoy)
    $stPC = $db->prepare("SELECT COALESCE(SUM(price),0) FROM attendances WHERE date=? AND pay_status='pendiente'");
    $stPC->execute([$today]); $porCobrar = (float)$stPC->fetchColumn();

    // Citas pendientes
    $stCP = $db->prepare("SELECT COUNT(*) FROM appointments WHERE date>=? AND status='pendiente'");
    $stCP->execute([$today]); $citasPend = (int)$stCP->fetchColumn();

    // Pacientes nuevos este mes
    $stPN = $db->prepare("SELECT COUNT(*) FROM patients WHERE DATE_FORMAT(created_at,'%Y-%m')=?");
    $stPN->execute([$month]); $pacNuevos = (int)$stPN->fetchColumn();

    // Total pacientes
    $totalPac = (int)$db->query("SELECT COUNT(*) FROM patients")->fetchColumn();

    // Ticket promedio del mes (atenciones pagadas)
    $stTP = $db->prepare(
        "SELECT COALESCE(AVG(price),0) FROM attendances
         WHERE pay_status='pagado' AND DATE_FORMAT(date,'%Y-%m')=?"
    );
    $stTP->execute([$month]); $ticketProm = round((float)$stTP->fetchColumn(), 2);

    // Ingresos últimos 7 días (para sparkline)
    $stSpark = $db->prepare(
        "SELECT date, SUM(price) as total FROM attendances
         WHERE date>=DATE_SUB(CURDATE(),INTERVAL 6 DAY) AND pay_status='pagado'
         GROUP BY date ORDER BY date ASC"
    );
    $stSpark->execute();
    $sparkData = $stSpark->fetchAll();

    // Top servicios del mes
    $stTop = $db->prepare(
        "SELECT service, COUNT(*) as count, SUM(price) as total
         FROM attendances
         WHERE DATE_FORMAT(date,'%Y-%m')=? AND pay_status='pagado' AND service!=''
         GROUP BY service ORDER BY total DESC LIMIT 5"
    );
    $stTop->execute([$month]);
    $topServices = $stTop->fetchAll();

    // Próximas citas (hoy y mañana)
    $stNext = $db->prepare(
        "SELECT a.*, p.first_name, p.last_name FROM appointments a
         LEFT JOIN patients p ON a.patient_id=p.id
         WHERE a.date>=? AND a.date<=DATE_ADD(?,INTERVAL 1 DAY) AND a.status='pendiente'
         ORDER BY a.date ASC, a.time ASC LIMIT 10"
    );
    $stNext->execute([$today, $today]);
    $nextAppts = $stNext->fetchAll();

    // Stock bajo
    $stLow = (int)$db->query(
        "SELECT COUNT(*) FROM inventory WHERE stock<=min_stock AND active=1"
    )->fetchColumn();

    jsonOk([
        'atencionesHoy'  => $attHoy,
        'ingresosHoy'    => $ingresosHoy,
        'porCobrar'      => $porCobrar,
        'citasPendientes'=> $citasPend,
        'pacientesNuevos'=> $pacNuevos,
        'totalPacientes' => $totalPac,
        'ticketPromedio' => $ticketProm,
        'sparkline'      => $sparkData,
        'topServices'    => $topServices,
        'nextAppts'      => $nextAppts,
        'lowStock'       => $stLow,
    ]);
}

// ─── COMISIONES ───────────────────────────────────────────────────────────────
if ($action === 'commissions' && $method === 'GET') {
    requirePerm($user, 'commissions');
    $range = strParam('range', 'month');
    $today = date('Y-m-d');
    $month = date('Y-m');

    // Esquema
    $schSt = $db->query("SELECT * FROM commission_scheme ORDER BY id ASC LIMIT 1");
    $scheme = $schSt->fetch() ?: ['pct' => 15, 'sales_pct' => 10];

    // Filtro fecha
    switch ($range) {
        case 'today': $dateWhere = "date='$today'"; break;
        case 'week':  $dateWhere = "date>=DATE_SUB(CURDATE(),INTERVAL 7 DAY)"; break;
        case 'month': $dateWhere = "DATE_FORMAT(date,'%Y-%m')='$month'"; break;
        default:      $dateWhere = "1=1";
    }

    // Comisiones por usuario
    $stUsers = $db->query("SELECT id,name,role FROM users WHERE active=1");
    $users   = $stUsers->fetchAll();
    $result  = [];

    foreach ($users as $u) {
        $uName = $u['name'];

        // Atenciones pagadas
        $stA = $db->prepare(
            "SELECT COUNT(*) as cnt, COALESCE(SUM(price),0) as total
             FROM attendances WHERE attended_by=? AND pay_status='pagado' AND $dateWhere"
        );
        $stA->execute([$uName]);
        $rowA = $stA->fetch();
        $attTotal = (float)$rowA['total'];
        $attComm  = $attTotal * ($scheme['pct'] / 100);

        // Ventas cobradas
        $stO = $db->prepare(
            "SELECT COUNT(*) as cnt, COALESCE(SUM(total),0) as total
             FROM sales_orders WHERE assigned_to=? AND status='cobrado' AND $dateWhere"
        );
        $stO->execute([$uName]);
        $rowO = $stO->fetch();
        $ordTotal = (float)$rowO['total'];
        $ordComm  = $ordTotal * (($scheme['sales_pct'] ?? 10) / 100);

        $result[] = [
            'user'        => $u,
            'attCount'    => (int)$rowA['cnt'],
            'attTotal'    => round($attTotal, 2),
            'attComm'     => round($attComm, 2),
            'ordersCount' => (int)$rowO['cnt'],
            'ordersTotal' => round($ordTotal, 2),
            'ordersComm'  => round($ordComm, 2),
            'totalComm'   => round($attComm + $ordComm, 2),
        ];
    }

    jsonOk(['scheme' => $scheme, 'commissions' => $result]);
}

// ─── CORTE DE CAJA ───────────────────────────────────────────────────────────
if ($action === 'cashbox_summary' && $method === 'GET') {
    requirePerm($user, 'cashbox');
    $range = strParam('range', 'today');
    $today = date('Y-m-d');
    $month = date('Y-m');

    switch ($range) {
        case 'today': $dateWhere = "date='$today'"; break;
        case 'week':  $dateWhere = "date>=DATE_SUB(CURDATE(),INTERVAL 7 DAY)"; break;
        case 'month': $dateWhere = "DATE_FORMAT(date,'%Y-%m')='$month'"; break;
        default:      $dateWhere = "1=1";
    }

    // Atenciones pagadas
    $stPA = $db->query(
        "SELECT pay_method, SUM(price) as total, COUNT(*) as count
         FROM attendances WHERE $dateWhere AND pay_status='pagado'
         GROUP BY pay_method"
    );
    $byMethod = [];
    $totalPaid = 0;
    foreach ($stPA->fetchAll() as $row) {
        $byMethod[$row['pay_method']] = round((float)$row['total'], 2);
        $totalPaid += (float)$row['total'];
    }

    // Pendientes
    $stPend = $db->query("SELECT COALESCE(SUM(price),0) as t, COUNT(*) as c FROM attendances WHERE $dateWhere AND pay_status='pendiente'");
    $rowPend = $stPend->fetch();

    // Anuladas
    $stAnul = $db->query("SELECT COUNT(*) as c FROM attendances WHERE $dateWhere AND pay_status='anulado'");
    $rowAnul = $stAnul->fetch();

    // Ventas cobradas
    $stSales = $db->query("SELECT COALESCE(SUM(total),0) as t FROM sales_orders WHERE $dateWhere AND status='cobrado'");
    $totalSales = (float)$stSales->fetchColumn();

    // Atenciones pendientes (detalle)
    $stPendList = $db->query(
        "SELECT a.*, p.first_name, p.last_name FROM attendances a
         LEFT JOIN patients p ON a.patient_id=p.id
         WHERE $dateWhere AND a.pay_status='pendiente'
         ORDER BY a.date DESC"
    );
    $pendList = $stPendList->fetchAll();

    jsonOk([
        'totalPaid'   => round($totalPaid, 2),
        'totalPend'   => round((float)$rowPend['t'], 2),
        'pendCount'   => (int)$rowPend['c'],
        'anulCount'   => (int)$rowAnul['c'],
        'totalSales'  => round($totalSales, 2),
        'grandTotal'  => round($totalPaid + $totalSales, 2),
        'byMethod'    => $byMethod,
        'pendList'    => $pendList,
    ]);
}

// ─── MARCAR ATENCIÓN COMO PAGADA ─────────────────────────────────────────────
if ($action === 'mark_paid' && $method === 'POST') {
    requirePerm($user, 'cashbox');
    $b = body();
    $attId  = (int)($b['id'] ?? 0);
    $method2 = trim((string)($b['pay_method'] ?? $b['payMethod'] ?? $b['paid_method'] ?? $b['paidMethod'] ?? 'Efectivo'));
    if ($method2 === '') $method2 = 'Efectivo';
    if (!$attId) jsonError('ID de atención requerido');

    $db->prepare(
        "UPDATE attendances SET pay_status='pagado', closed=1, pay_method=? WHERE id=?"
    )->execute([$method2, $attId]);

    $st = $db->prepare("SELECT * FROM attendances WHERE id=? LIMIT 1");
    $st->execute([$attId]);
    $row = $st->fetch() ?: [];
    if ($row) {
        $row['patientId'] = $row['patient_id'] ?? null;
        $row['patientName'] = $row['patient_name'] ?? '';
        $row['payMethod'] = $row['pay_method'] ?? '';
        $row['payStatus'] = $row['pay_status'] ?? '';
        $row['attendedBy'] = $row['attended_by'] ?? '';
    }
    jsonOk($row);
}

// ─── ANULAR ATENCIÓN ─────────────────────────────────────────────────────────
if ($action === 'anular' && $method === 'POST') {
    requirePerm($user, 'cashbox');
    $b = body();
    $attId  = (int)($b['id'] ?? 0);
    $motivo = $b['motivo'] ?? '';
    if (!$attId) jsonError('ID requerido');

    $stCols = $db->prepare("DESCRIBE `attendances`");
    $stCols->execute();
    $validCols = array_column($stCols->fetchAll(), 'Field');
    $updates = ['pay_status' => 'anulado', 'closed' => 0];
    if (in_array('anular_motivo', $validCols, true)) $updates['anular_motivo'] = $motivo;
    if (in_array('anulado_motivo', $validCols, true)) $updates['anulado_motivo'] = $motivo;
    if (in_array('anulado_por', $validCols, true)) $updates['anulado_por'] = $user['name'] ?? 'Sistema';
    if (in_array('anulado_at', $validCols, true)) $updates['anulado_at'] = date('Y-m-d');

    $setStr = implode(',', array_map(fn($c) => "`$c`=?", array_keys($updates)));
    $vals = array_values($updates);
    $vals[] = $attId;
    $db->prepare("UPDATE attendances SET $setStr WHERE id=?")->execute($vals);

    $st = $db->prepare("SELECT * FROM attendances WHERE id=? LIMIT 1");
    $st->execute([$attId]);
    $row = $st->fetch() ?: ['id' => $attId, 'pay_status' => 'anulado'];
    $row['payStatus'] = $row['pay_status'] ?? 'anulado';
    $row['anuladoMotivo'] = $row['anulado_motivo'] ?? $row['anular_motivo'] ?? $motivo;
    $row['anuladoPor'] = $row['anulado_por'] ?? ($user['name'] ?? 'Sistema');
    jsonOk($row);
}

// ─── REPORTES ────────────────────────────────────────────────────────────────
if ($action === 'reports' && $method === 'GET') {
    requirePerm($user, 'reports');
    $from  = strParam('from', date('Y-m-01'));
    $to    = strParam('to', date('Y-m-d'));
    $type  = strParam('type', 'revenue');

    switch ($type) {
        case 'revenue':
            $st = $db->prepare(
                "SELECT date, SUM(price) as total, COUNT(*) as count
                 FROM attendances WHERE date BETWEEN ? AND ? AND pay_status='pagado'
                 GROUP BY date ORDER BY date ASC"
            );
            $st->execute([$from, $to]);
            jsonOk(['data' => $st->fetchAll(), 'from' => $from, 'to' => $to]);

        case 'services':
            $st = $db->prepare(
                "SELECT service, COUNT(*) as count, SUM(price) as total
                 FROM attendances WHERE date BETWEEN ? AND ? AND pay_status='pagado' AND service!=''
                 GROUP BY service ORDER BY total DESC"
            );
            $st->execute([$from, $to]);
            jsonOk(['data' => $st->fetchAll()]);

        case 'patients':
            $st = $db->prepare(
                "SELECT DATE_FORMAT(created_at,'%Y-%m') as month, COUNT(*) as count
                 FROM patients WHERE created_at BETWEEN ? AND ?
                 GROUP BY month ORDER BY month ASC"
            );
            $st->execute([$from . ' 00:00:00', $to . ' 23:59:59']);
            jsonOk(['data' => $st->fetchAll()]);

        case 'by_staff':
            $st = $db->prepare(
                "SELECT attended_by, COUNT(*) as count, SUM(price) as total
                 FROM attendances WHERE date BETWEEN ? AND ? AND pay_status='pagado'
                 GROUP BY attended_by ORDER BY total DESC"
            );
            $st->execute([$from, $to]);
            jsonOk(['data' => $st->fetchAll()]);

        default:
            jsonError("Tipo de reporte desconocido: $type");
    }
}

// ─── BACKUP COMPLETO ─────────────────────────────────────────────────────────
// ─── BACKUP POR TIPO ─────────────────────────────────────────────────────────
// ?action=backup&type=general|patients|cashbox|clinical
// type=general   → todo el sistema (default)
// type=patients  → solo pacientes + usuarios
// type=cashbox   → caja, atenciones, ventas, pagos
// type=clinical  → historias clínicas (pacientes + atenciones + fotos + laboratorio)
if ($action === 'backup' && $method === 'GET') {
    requirePerm($user, 'admin');

    $type = $_GET['type'] ?? 'general';

    $jsonFields = ['pathologies','path_ant','zone_left','zone_right','nails_l','nails_r',
                   'photos','items','by_method','podo_functions','perms'];

    $tableGroups = [
        'general'  => ['patients','appointments','attendances','services','inventory',
                       'suppliers','sales_products','sales_services','sales_orders',
                       'payments','categories','laboratory','photos','memberships',
                       'commission_scheme','company','users','cashbox_cuts','module_perms'],
        'patients' => ['patients','users','categories','services'],
        'cashbox'  => ['cashbox_cuts','attendances','sales_orders','payments','commission_scheme','sales_products','sales_services'],
        'clinical' => ['patients','attendances','appointments','laboratory','photos'],
    ];

    $tables = $tableGroups[$type] ?? $tableGroups['general'];
    $backup = [
        '_version' => '4.0',
        '_date'    => date('c'),
        '_source'  => 'PHP/MySQL',
        '_type'    => $type,
    ];

    foreach ($tables as $t) {
        try {
            $st   = $db->query("SELECT * FROM `$t`");
            $rows = $st->fetchAll();
            foreach ($rows as &$row) {
                foreach ($jsonFields as $f) {
                    if (isset($row[$f]) && is_string($row[$f])) {
                        $row[$f] = json_decode($row[$f], true);
                    }
                }
                unset($row['password_hash']);
            }
            unset($row);
            $backup[$t] = $rows;
        } catch (Throwable $e) {
            $backup[$t] = [];
        }
    }
    jsonOk($backup);
}

// ─── RESTAURAR BACKUP ────────────────────────────────────────────────────────
// ── Helper: normalizar DNI de paciente para evitar duplicados ────────────────
// Pacientes con DNI vacío, falso ('123456789') o guion ('-') reciben un DNI
// temporal único 'SIN-{id}' para que TODOS ingresen a la BD sin excepción.
function fixPatientDni(array &$mapped, array &$seenDnis): void {
    $badDnis = ['', '123456789', '-'];
    $dni     = trim($mapped['dni'] ?? '');
    $id      = $mapped['id'] ?? null;

    if (in_array($dni, $badDnis, true) || isset($seenDnis[$dni])) {
        // Asignar DNI temporal basado en el id original del paciente
        $suffix  = $id ? str_pad((string)$id, 5, '0', STR_PAD_LEFT) : uniqid();
        $newDni  = 'SIN-' . $suffix;
        // Garantizar unicidad si ya existe ese SIN-xxxxx
        while (isset($seenDnis[$newDni])) {
            $newDni = 'SIN-' . $suffix . '-' . substr(uniqid(), -4);
        }
        $mapped['dni'] = $newDni;
    }
    $seenDnis[$mapped['dni']] = true;
}

function normalizeImportRow(array $row, string $table, array $validCols, array $colMap, array $jsonFields, array &$seenPatientDnis): array {
    $mapped = [];
    foreach ($row as $k => $v) {
        $col = $colMap[$k] ?? null;
        if ($col) {
            $mapped[$col] = $v;
        } else {
            $snake = strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($k)));
            $mapped[$snake] = $v;
        }
    }

    if ($table === 'patients') {
        if (isset($mapped['created_at']) && !isset($mapped['registration_date'])) {
            $mapped['registration_date'] = $mapped['created_at'];
            unset($mapped['created_at']);
        }
        if (!isset($mapped['first_name']) || trim((string)$mapped['first_name']) === '') {
            $mapped['first_name'] = '-';
        }
        if (!isset($mapped['last_name']) || trim((string)$mapped['last_name']) === '') {
            $mapped['last_name'] = '-';
        }
        fixPatientDni($mapped, $seenPatientDnis);
    }
    if ($table === 'appointments' && isset($mapped['status'])) {
        $s = strtolower(trim((string)$mapped['status']));
        $statusMap = [
            'pendiente' => 'pending',
            'confirmado' => 'confirmed',
            'confirmada' => 'confirmed',
            'atendida' => 'done',
            'atendido' => 'done',
            'cancelado' => 'cancelled',
            'cancelada' => 'cancelled',
        ];
        $mapped['status'] = $statusMap[$s] ?? $s;
    }

    foreach ($jsonFields as $jf) {
        $snakeJf = $colMap[$jf] ?? strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($jf)));
        if (isset($mapped[$snakeJf]) && is_array($mapped[$snakeJf])) {
            $mapped[$snakeJf] = json_encode($mapped[$snakeJf], JSON_UNESCAPED_UNICODE);
        }
        if (isset($mapped[$jf]) && is_array($mapped[$jf])) {
            $mapped[$jf] = json_encode($mapped[$jf], JSON_UNESCAPED_UNICODE);
        }
    }

    unset($mapped['password_hash']);
    return array_intersect_key($mapped, array_flip($validCols));
}

function insertImportBatch(PDO $db, string $table, array $cols, array $rows): void {
    if (!$rows) return;
    $colSql = implode(',', array_map(fn($c) => "`$c`", $cols));
    $oneRow = '(' . implode(',', array_fill(0, count($cols), '?')) . ')';
    $valuesSql = implode(',', array_fill(0, count($rows), $oneRow));
    $params = [];
    foreach ($rows as $row) {
        foreach ($cols as $c) $params[] = $row[$c] ?? null;
    }
    $db->prepare("INSERT IGNORE INTO `$table` ($colSql) VALUES $valuesSql")->execute($params);
}

function importRowsFast(PDO $db, string $table, array $rows, array $validCols, array $colMap, array $jsonFields, array &$seenPatientDnis, array &$errors): int {
    $groups = [];
    $count = 0;
    $batchSize = 40;

    foreach ($rows as $row) {
        if (!is_array($row)) continue;
        $filtered = normalizeImportRow($row, $table, $validCols, $colMap, $jsonFields, $seenPatientDnis);
        if (!$filtered) continue;
        if ($table === 'attendances') {
            unset($filtered['id']);
        }

        $cols = array_keys($filtered);
        sort($cols);
        $sig = implode('|', $cols);
        if (!isset($groups[$sig])) $groups[$sig] = ['cols' => $cols, 'rows' => []];
        $groups[$sig]['rows'][] = $filtered;

        if (count($groups[$sig]['rows']) >= $batchSize) {
            try {
                insertImportBatch($db, $table, $groups[$sig]['cols'], $groups[$sig]['rows']);
                $count += count($groups[$sig]['rows']);
            } catch (Throwable $e) {
                $errors[] = "$table: " . substr($e->getMessage(), 0, 120);
            }
            $groups[$sig]['rows'] = [];
        }
    }

    foreach ($groups as $group) {
        if (!$group['rows']) continue;
        try {
            insertImportBatch($db, $table, $group['cols'], $group['rows']);
            $count += count($group['rows']);
        } catch (Throwable $e) {
            $errors[] = "$table: " . substr($e->getMessage(), 0, 120);
        }
    }

    return $count;
}

if ($action === 'restore' && $method === 'POST') {
    requirePerm($user, 'admin');
    $b = body();
    if (!$b || !is_array($b)) jsonError('Datos de restauración inválidos');

    // Mapa: clave del backup (camelCase o snake_case) → tabla MySQL
    $keyMap = [
        'categories'    => 'categories',
        'services'      => 'services',
        'suppliers'     => 'suppliers',
        'salesProducts' => 'sales_products',
        'sales_products'=> 'sales_products',
        'salesServices' => 'sales_services',
        'sales_services'=> 'sales_services',
        'salesOrders'   => 'sales_orders',
        'sales_orders'  => 'sales_orders',
        'payments'      => 'payments',
        'laboratory'    => 'laboratory',
        'photos'        => 'photos',
        'memberships'   => 'memberships',
        'commission_scheme' => 'commission_scheme',
        'company'       => 'company',
        // Tablas con FK: borrar en orden correcto
        'appointments'  => 'appointments',
        'attendances'   => 'attendances',
        'patients'      => 'patients',
        'inventory'     => 'inventory',
    ];

    $jsonFields = ['pathologies','path_ant','zone_left','zone_right','nails_l','nails_r',
                   'photos','items','by_method','podo_functions','perms','order_items','orderItems'];

    $colMap = [
        'firstName'       => 'first_name',
        'lastName'        => 'last_name',
        'shoeSize'        => 'shoe_size',
        'shoeType'        => 'shoe_type',
        'physicalActivity'=> 'physical_activity',
        'pathAnt'         => 'path_ant',
        'zoneLeft'        => 'zone_left',
        'zoneRight'       => 'zone_right',
        'nailColor'       => 'nail_color',
        'nailThickness'   => 'nail_thick',
        'nailShape'       => 'nail_shape',
        'emergencyContact'=> 'emergency_contact',
        'emergencyPhone'  => 'emergency_phone',
        'attendedBy'      => 'attended_by',
        'patientId'       => 'patient_id',
        'patientName'     => 'patient_name',
        'payMethod'       => 'pay_method',
        'payStatus'       => 'pay_status',
        'orderItems'      => 'photos',
        'minStock'        => 'min_stock',
        'registrationDate'=> 'registration_date',
    ];

    $results = [];
    $errors  = [];

    try {
        // 1. Limpiar en orden FK-safe (sin transacción para TRUNCATE)
        $clearOrder = ['photos','laboratory','attendances','appointments',
                       'patients','sales_orders','payments','memberships',
                       'inventory','sales_products','sales_services',
                       'suppliers','services','categories'];
        foreach ($clearOrder as $tbl) {
            try { $db->exec("DELETE FROM `$tbl`"); } catch (Throwable $e) {}
        }

        // 2. Actualizar company si viene
        if (isset($b['company']) && is_array($b['company'])) {
            $co = $b['company'];
            $db->prepare("UPDATE `company` SET name=?, currency=?, currency_code=?, address=?, phone=?, email=? WHERE id=1")
               ->execute([$co['name'] ?? '', $co['currency'] ?? 'S/', $co['currencyCode'] ?? 'PEN',
                          $co['address'] ?? '', $co['phone'] ?? '', $co['email'] ?? '']);
            $results['company'] = 'actualizado';
        }

        // 3. Obtener columnas válidas de cada tabla (cache)
        $colCache = [];
        $getValidCols = function(string $tbl) use ($db, &$colCache): array {
            if (!isset($colCache[$tbl])) {
                $colCache[$tbl] = array_column($db->query("DESCRIBE `$tbl`")->fetchAll(), 'Field');
            }
            return $colCache[$tbl];
        };

        // 4. Restaurar cada tabla
        $seenPatientDnis = []; // rastrear DNIs ya usados en esta importación
        foreach ($keyMap as $bKey => $table) {
            if ($table === 'company') continue;
            $data = $b[$bKey] ?? null;
            if (!$data || !is_array($data) || count($data) === 0) continue;

            $validCols = $getValidCols($table);
            $count = 0;

            $db->beginTransaction();
            try {
                foreach ($data as $row) {
                    if (!is_array($row)) continue;

                    // Renombrar camelCase → snake_case
                    $mapped = [];
                    foreach ($row as $k => $v) {
                        $col = $colMap[$k] ?? null;
                        if ($col) $mapped[$col] = $v;
                        else      $mapped[$k]   = $v;
                    }

                    // ── Fix DNI para pacientes: garantizar que TODOS entren ──
                    if ($table === 'patients') {
                        if (!isset($mapped['first_name']) || trim($mapped['first_name']) === '') {
                            $mapped['first_name'] = '-';
                        }
                        if (!isset($mapped['last_name']) || trim($mapped['last_name']) === '') {
                            $mapped['last_name'] = '-';
                        }
                        fixPatientDni($mapped, $seenPatientDnis);
                    }

                    // Encode JSON fields
                    foreach ($jsonFields as $jf) {
                        if (isset($mapped[$jf]) && is_array($mapped[$jf])) {
                            $mapped[$jf] = json_encode($mapped[$jf], JSON_UNESCAPED_UNICODE);
                        }
                        $snakeJf = $colMap[$jf] ?? $jf;
                        if (isset($mapped[$snakeJf]) && is_array($mapped[$snakeJf])) {
                            $mapped[$snakeJf] = json_encode($mapped[$snakeJf], JSON_UNESCAPED_UNICODE);
                        }
                    }

                    unset($mapped['password_hash']);
                    $filtered = array_intersect_key($mapped, array_flip($validCols));
                    if (empty($filtered)) continue;

                    $cols = implode(',', array_map(fn($c) => "`$c`", array_keys($filtered)));
                    $phs  = implode(',', array_fill(0, count($filtered), '?'));
                    try {
                        $db->prepare("INSERT INTO `$table` ($cols) VALUES ($phs) ON DUPLICATE KEY UPDATE id=id")
                           ->execute(array_values($filtered));
                        $count++;
                    } catch (Throwable $e) {
                        $errors[] = "$table: " . substr($e->getMessage(), 0, 80);
                    }
                }
                $db->commit();
                $results[$bKey] = "$count → $table";
            } catch (Throwable $e) {
                $db->rollBack();
                $errors[] = "Rollback $table: " . $e->getMessage();
            }
        }

        jsonOk([
            'ok'      => true,
            'message' => 'Restauración completada',
            'results' => $results,
            'errors'  => $errors,
        ]);
    } catch (Throwable $e) {
        jsonError('Error durante restauración: ' . $e->getMessage(), 500);
    }
}

// ─── IA PROXY: IMPORTAR PDF ───────────────────────────────────────────────────
if ($action === 'ai_import_pdf' && $method === 'POST') {
    $b = body();
    $b64    = $b['b64'] ?? '';
    $apiKey = $b['apiKey'] ?? '';
    if (!$b64) jsonError('PDF data requerido');

    // Obtener API key de la empresa si no se pasó
    if (!$apiKey) {
        $co = $db->query("SELECT ai_api_key FROM company LIMIT 1")->fetch();
        $apiKey = $co['ai_api_key'] ?? '';
    }
    if (!$apiKey) {
        jsonOk(['content' => [['type' => 'text', 'text' => 'No hay clave API de Claude configurada. Vaya a Configuración → Empresa → Clave API Claude.']]]);
    }

    $payload = json_encode([
        'model'      => 'claude-haiku-4-5-20251001',
        'max_tokens' => 2000,
        'messages'   => [[
            'role'   => 'user',
            'content'=> [
                ['type' => 'document', 'source' => ['type' => 'base64', 'media_type' => 'application/pdf', 'data' => $b64]],
                ['type' => 'text', 'text' => "Extrae los datos de paciente(s) de este documento de historia clínica podológica.\nDevuelve SOLO un array JSON con objetos. Campos posibles:\nfirstName (Nombres), lastName (Apellidos), dni (DNI/Carnet), dob (fecha nacimiento YYYY-MM-DD),\nsex (M/F), phone (Celular/Teléfono), email, address (Dirección),\noccupation (Ocupación/Profesión), weight (Peso kg), height (Talla cm),\nshoeSize (Talla calzado), shoeType (Tipo calzado),\npathologies (array de patologías), pathAnt (array antecedentes),\nsmoke (Fuma: Si/No), alcohol (Alcohol: Si/No), meds (Medicamentos),\nemergencyContact (Contacto emergencia), emergencyPhone (Tel emergencia),\nnotes (Observaciones/Notas).\nSi no encuentras un campo déjalo vacío. Devuelve SOLO el JSON, sin explicaciones."],
            ]
        ]],
    ]);

    $ch = curl_init('https://api.anthropic.com/v1/messages');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            "x-api-key: $apiKey",
            'anthropic-version: 2023-06-01',
        ],
        CURLOPT_TIMEOUT        => 60,
    ]);
    $res = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err) jsonError("cURL error: $err", 500);
    echo $res;
    exit;
}

// ─── IA PROXY: SUGERENCIA CLÍNICA ────────────────────────────────────────────
if ($action === 'ai_suggest' && $method === 'POST') {
    $b      = body();
    $prompt = $b['prompt'] ?? '';
    $apiKey = $b['apiKey'] ?? '';
    if (!$prompt) jsonError('Prompt requerido');

    if (!$apiKey) {
        $co = $db->query("SELECT ai_api_key FROM company LIMIT 1")->fetch();
        $apiKey = $co['ai_api_key'] ?? '';
    }
    if (!$apiKey) jsonError('No hay clave API configurada.');

    $payload = json_encode([
        'model'      => 'claude-haiku-4-5-20251001',
        'max_tokens' => 800,
        'messages'   => [['role' => 'user', 'content' => $prompt]],
    ]);

    $ch = curl_init('https://api.anthropic.com/v1/messages');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            "x-api-key: $apiKey",
            'anthropic-version: 2023-06-01',
        ],
        CURLOPT_TIMEOUT        => 30,
    ]);
    $res = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err) jsonError("cURL error: $err", 500);
    echo $res;
    exit;
}

// ─── ESQUEMA DE COMISIONES (singleton) ───────────────────────────────────────
if ($action === 'commission_scheme') {
    if ($method === 'GET') {
        requirePerm($user, 'commissions');
        $st = $db->query("SELECT * FROM commission_scheme ORDER BY id ASC LIMIT 1");
        $row = $st->fetch() ?: ['id'=>1,'pct'=>15,'sales_pct'=>10];
        jsonOk(['pct' => (float)($row['pct'] ?? 15), 'salesPct' => (float)($row['sales_pct'] ?? 10)]);
    }
    if ($method === 'PUT') {
        requireSA($user); // Solo Super Admin puede modificar esquema de comisiones
        $b = body();
        $pct = max(0, min(100, (float)($b['pct'] ?? 15)));
        $salesPct = max(0, min(100, (float)($b['salesPct'] ?? $b['sales_pct'] ?? 10)));
        try {
            $db->beginTransaction();
            $row = $db->query("SELECT id FROM commission_scheme ORDER BY id ASC LIMIT 1")->fetch();
            if ($row) {
                $id = (int)$row['id'];
                $db->prepare("UPDATE commission_scheme SET pct=?, sales_pct=? WHERE id=?")->execute([$pct, $salesPct, $id]);
                $db->prepare("DELETE FROM commission_scheme WHERE id<>?")->execute([$id]);
            } else {
                $db->prepare("INSERT INTO commission_scheme (pct,sales_pct) VALUES (?,?)")->execute([$pct, $salesPct]);
                $id = (int)$db->lastInsertId();
            }
            $st = $db->prepare("SELECT pct,sales_pct FROM commission_scheme WHERE id=? LIMIT 1");
            $st->execute([$id]);
            $saved = $st->fetch();
            $db->commit();
            jsonOk([
                'ok' => true,
                'pct' => (float)($saved['pct'] ?? $pct),
                'salesPct' => (float)($saved['sales_pct'] ?? $salesPct),
                'message' => 'Esquema de comisiones guardado en la base de datos',
            ]);
        } catch (Throwable $e) {
            if ($db->inTransaction()) $db->rollBack();
            jsonError('No se pudo guardar el esquema de comisiones en la BD: ' . $e->getMessage(), 500);
        }
    }
}

// ─── PERMISOS DE MÓDULOS ─────────────────────────────────────────────────────
// GET: devuelve permisos actuales
// POST: guarda permisos (Solo Super Admin)
if ($action === 'module_perms') {
    if ($method === 'GET') {
        // Ya manejado arriba (acción pública) — no debería llegar aquí
        jsonOk(['perms' => [], 'lockedOff' => []]);
        $result = [];
        foreach ($rows as $row) {
            $result[$row['role']] = json_decode($row['perms'], true) ?? [];
        }
        // También devolver lockedOff si está guardado en config
        $lockedSt = $db->prepare("SELECT val FROM config WHERE `key`='module_locked_off' LIMIT 1");
        $lockedSt->execute();
        $lockedRow = $lockedSt->fetch();
        $lockedOff = $lockedRow ? json_decode($lockedRow['val'], true) : [];
        jsonOk(['perms' => $result, 'lockedOff' => $lockedOff]);
    }
    if ($method === 'POST') {
        requireSA($user); // Solo Super Admin puede guardar permisos
        $b = body();
        $perms     = $b['perms']     ?? [];
        $lockedOff = $b['lockedOff'] ?? [];

        if (!is_array($perms)) jsonError('Formato inválido');

        $db->beginTransaction();
        try {
            foreach ($perms as $role => $rolePerms) {
                $encoded = json_encode(array_values($rolePerms), JSON_UNESCAPED_UNICODE);
                $exists = $db->prepare("SELECT id FROM module_perms WHERE role=? LIMIT 1");
                $exists->execute([$role]);
                if ($exists->fetch()) {
                    $db->prepare("UPDATE module_perms SET perms=? WHERE role=?")->execute([$encoded, $role]);
                } else {
                    $db->prepare("INSERT INTO module_perms (role, perms) VALUES (?,?)")->execute([$role, $encoded]);
                }
            }
            // Guardar lockedOff en config
            $lockedEncoded = json_encode($lockedOff, JSON_UNESCAPED_UNICODE);
            $db->prepare("INSERT INTO config (`key`,val) VALUES ('module_locked_off',?) ON DUPLICATE KEY UPDATE val=?")
               ->execute([$lockedEncoded, $lockedEncoded]);
            $db->commit();
            jsonOk(['ok' => true, 'saved' => count($perms).' roles']);
        } catch (Throwable $e) {
            $db->rollBack();
            jsonError('Error al guardar permisos: ' . $e->getMessage(), 500);
        }
    }
}


// ─── IMPORTAR DESDE SISTEMA LOCAL (PC) ───────────────────────────────────────
// Acepta el formato JSON del sistema Node.js/JSON de escritorio
// Compatible con data.zip / backup exportado desde la app de PC
if ($action === 'import_local' && $method === 'POST') {
    requirePerm($user, 'admin');
    if ($user['role'] !== 'Super Admin') {
        jsonError('Solo el Super Admin puede importar datos desde el sistema local', 403);
    }

    $b = body();
    if (!$b) jsonError('No se recibieron datos');

    // Detectar formato: puede venir como backup completo o como objeto de archivos JSON
    // Formato A: {patients:[...], attendances:[...], ...}  ← backup estándar
    // Formato B: {files: {patients:[...], attendances:[...]}} ← wrapper del sistema local
    $data = isset($b['files']) ? $b['files'] : $b;
    $shouldClear = !array_key_exists('_clear', $b) || (bool)$b['_clear'];

    // Mapa: clave JSON del sistema local → tabla MySQL + función de transformación
    $importMap = [
        'categories'    => 'categories',
        'services'      => 'services',
        'suppliers'     => 'suppliers',
        'salesProducts' => 'sales_products',
        'sales_products'=> 'sales_products',
        'salesServices' => 'sales_services',
        'sales_services'=> 'sales_services',
        'inventory'     => 'inventory',
        'patients'      => 'patients',
        'appointments'  => 'appointments',
        'attendances'   => 'attendances',
        'photos'        => 'photos',
        'laboratory'    => 'laboratory',
        'memberships'   => 'memberships',
        'salesOrders'   => 'sales_orders',
        'sales_orders'  => 'sales_orders',
        'payments'      => 'payments',
        'cashbox'       => 'cashbox_cuts',
        'cashbox_cuts'  => 'cashbox_cuts',
    ];

    // Campo de transformación: JSON fields que necesitan encode
    $jsonFields = ['pathologies','path_ant','zone_left','zone_right','nails_l','nails_r',
                   'photos','items','by_method','podo_functions','perms','order_items',
                   'orderItems','customPathologies','customAntecedents','customTags',
                   'zoneLeft','zoneRight','nailColor','nailThickness','nailShape'];

    // Mapa de renombre de columnas: camelCase → snake_case
    $colMap = [
        'firstName'       => 'first_name',
        'lastName'        => 'last_name',
        'shoeSize'        => 'shoe_size',
        'shoeType'        => 'shoe_type',
        'physicalActivity'=> 'physical_activity',
        'pathAnt'         => 'path_ant',
        'zoneLeft'        => 'zone_left',
        'zoneRight'       => 'zone_right',
        'nailColor'       => 'nail_color',
        'nailThickness'   => 'nail_thick',
        'nailShape'       => 'nail_shape',
        'emergencyContact'=> 'emergency_contact',
        'emergencyPhone'  => 'emergency_phone',
        'attendedBy'      => 'attended_by',
        'patientId'       => 'patient_id',
        'patientName'     => 'patient_name',
        'payMethod'       => 'pay_method',
        'payStatus'       => 'pay_status',
        'orderItems'      => 'photos',   // attendances.orderItems → photos JSON
        'attType'         => 'att_type',
        'minStock'        => 'min_stock',
        'createdAt'       => 'created_at',
        'registrationDate'=> 'created_at',
    ];

    $results = [];
    $errors  = [];

    $db->beginTransaction();
    try {
        // 1. Importar company y commission_scheme si existen
        if (isset($data['company']) && is_array($data['company'])) {
            $co = $data['company'];
            $db->prepare(
                "UPDATE `company` SET name=?, currency=?, currency_code=?, address=?, phone=?, email=? WHERE id=1"
            )->execute([
                $co['name'] ?? 'Podología Más Bela',
                $co['currency'] ?? 'S/',
                $co['currencyCode'] ?? 'PEN',
                $co['address'] ?? '',
                $co['phone'] ?? '',
                $co['email'] ?? '',
            ]);
            $results['company'] = 'actualizado';
        }

        $schemeData = $data['commission_scheme'] ?? $data['commissionScheme'] ?? null;
        if (is_array($schemeData)) {
            $cs = $schemeData;
            $pct = max(0, min(100, (float)($cs['pct'] ?? 15)));
            $salesPct = max(0, min(100, (float)($cs['salesPct'] ?? $cs['sales_pct'] ?? 10)));
            $row = $db->query("SELECT id FROM `commission_scheme` ORDER BY id ASC LIMIT 1")->fetch();
            if ($row) {
                $id = (int)$row['id'];
                $db->prepare("UPDATE `commission_scheme` SET pct=?, sales_pct=? WHERE id=?")->execute([$pct, $salesPct, $id]);
                $db->prepare("DELETE FROM `commission_scheme` WHERE id<>?")->execute([$id]);
            } else {
                $db->prepare("INSERT INTO `commission_scheme` (pct,sales_pct) VALUES (?,?)")->execute([$pct, $salesPct]);
            }
            $results['commission_scheme'] = 'actualizado';
        }

        if (isset($data['modulePerms']) && is_array($data['modulePerms'])) {
            foreach ($data['modulePerms'] as $role => $perms) {
                $encoded = json_encode(array_values($perms), JSON_UNESCAPED_UNICODE);
                $db->prepare(
                    "INSERT INTO module_perms (role,perms) VALUES (?,?) ON DUPLICATE KEY UPDATE perms=?"
                )->execute([$role, $encoded, $encoded]);
            }
            $results['modulePerms'] = count($data['modulePerms']).' roles';
        }

        // 2. Importar usuarios
        if (isset($data['users']) && is_array($data['users'])) {
            $validRoles = ['Super Admin','Administrador','Podólogo','Recepcionista'];
            $seen = [];
            foreach ($data['users'] as $u) {
                $uname = $u['username'] ?? '';
                if (!$uname || in_array($uname, $seen)) continue;
                $seen[] = $uname;
                $role = in_array($u['role'] ?? '', $validRoles) ? $u['role'] : 'Recepcionista';
                $funcs = json_encode($u['podoFunctions'] ?? [], JSON_UNESCAPED_UNICODE);
                $active = ($u['active'] ?? true) ? 1 : 0;
                $db->prepare(
                    "INSERT INTO users (username,password_hash,name,role,dni,address,cellphone,email,podo_functions,active)
                     VALUES (?,?,?,?,?,?,?,?,?,?)
                     ON DUPLICATE KEY UPDATE name=VALUES(name),role=VALUES(role),podo_functions=VALUES(podo_functions),active=VALUES(active)"
                )->execute([
                    $uname, '',
                    $u['name'] ?? '',
                    $role,
                    $u['dni'] ?? '',
                    $u['address'] ?? '',
                    $u['cellphone'] ?? '',
                    $u['email'] ?? '',
                    $funcs,
                    $active,
                ]);
            }
            $results['users'] = count($seen).' usuarios';
        }

        // 3. Limpiar tablas en orden correcto (respetar FK). En importación por lotes
        // solo se ejecuta en la primera llamada.
        if ($shouldClear) {
            $clearOrder = ['photos','laboratory','attendances','appointments',
                           'patients','cashbox_cuts','sales_orders','payments',
                           'memberships','inventory','sales_products','sales_services',
                           'suppliers','services','categories'];
            foreach ($clearOrder as $tbl) {
                $db->exec("DELETE FROM `$tbl`");
            }
            $results['_clear'] = 'tablas limpiadas';
        }

        // 4. Importar tablas mapeadas
        $seenPatientDnis = []; // rastrear DNIs ya usados en esta importación
        foreach ($importMap as $jsonKey => $table) {
            if (!isset($data[$jsonKey]) || !is_array($data[$jsonKey]) || count($data[$jsonKey]) === 0) continue;

            // Obtener columnas válidas de la tabla
            $validCols = array_column($db->query("DESCRIBE `$table`")->fetchAll(), 'Field');
            $count = importRowsFast($db, $table, $data[$jsonKey], $validCols, $colMap, $jsonFields, $seenPatientDnis, $errors);
            $results[$jsonKey] = "$count registros → $table";
            continue;
            $count = 0;

            foreach ($data[$jsonKey] as $row) {
                if (!is_array($row)) continue;

                // Renombrar claves camelCase a snake_case
                $mapped = [];
                foreach ($row as $k => $v) {
                    $col = $colMap[$k] ?? null;
                    if ($col) {
                        $mapped[$col] = $v;
                    } else {
                        $snake = strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($k)));
                        $mapped[$snake] = $v;
                    }
                }

                // ── Fix DNI para pacientes: garantizar que TODOS entren ──
                if ($table === 'patients') {
                    if (!isset($mapped['first_name']) || trim($mapped['first_name']) === '') {
                        $mapped['first_name'] = '-';
                    }
                    if (!isset($mapped['last_name']) || trim($mapped['last_name']) === '') {
                        $mapped['last_name'] = '-';
                    }
                    fixPatientDni($mapped, $seenPatientDnis);
                }

                // Encode JSON fields
                foreach ($jsonFields as $jf) {
                    $snakeJf = $colMap[$jf] ?? strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($jf)));
                    if (isset($mapped[$snakeJf]) && is_array($mapped[$snakeJf])) {
                        $mapped[$snakeJf] = json_encode($mapped[$snakeJf], JSON_UNESCAPED_UNICODE);
                    }
                    if (isset($mapped[$jf]) && is_array($mapped[$jf])) {
                        $mapped[$jf] = json_encode($mapped[$jf], JSON_UNESCAPED_UNICODE);
                    }
                }

                // Filtrar solo columnas que existen en la tabla
                $filtered = array_intersect_key($mapped, array_flip($validCols));
                if (empty($filtered)) continue;

                $cols = implode(',', array_map(fn($c) => "`$c`", array_keys($filtered)));
                $phs  = implode(',', array_fill(0, count($filtered), '?'));
                try {
                    $db->prepare("INSERT IGNORE INTO `$table` ($cols) VALUES ($phs)")
                       ->execute(array_values($filtered));
                    $count++;
                } catch (Throwable $e) {
                    $errors[] = "$table: " . $e->getMessage();
                }
            }
            $results[$jsonKey] = "$count registros → $table";
        }

        $db->commit();
        jsonOk([
            'ok'      => true,
            'results' => $results,
            'errors'  => $errors,
            'message' => 'Importación completada desde sistema local',
        ]);

    } catch (Throwable $e) {
        $db->rollBack();
        jsonError('Error durante importación: ' . $e->getMessage(), 500);
    }
}

// ─── MOVIMIENTO DE STOCK (descontar inventario al vender) ────────────────────
if ($action === 'stock_move' && $method === 'POST') {
    requirePerm($user, 'cashbox');
    $b = body();
    $items     = $b['items']     ?? [];
    $orderCode = $b['order_code'] ?? '';
    $date      = $b['date']       ?? date('Y-m-d');

    $db->beginTransaction();
    try {
        foreach ($items as $item) {
            $id     = (int)($item['id'] ?? 0);
            $source = $item['source'] ?? 'catalog';
            $qty    = (int)($item['qty'] ?? 1);
            if ($id <= 0 || $qty <= 0) continue;

            // Solo descontar PRODUCTOS — los servicios no tienen stock
            if ($source === 'catalog') {
                $db->prepare(
                    "UPDATE `sales_products` SET `stock` = GREATEST(0, COALESCE(stock,0) - ?) WHERE id = ?"
                )->execute([$qty, $id]);
            } elseif ($source === 'inventory') {
                $db->prepare(
                    "UPDATE `inventory` SET `stock` = GREATEST(0, COALESCE(stock,0) - ?) WHERE id = ?"
                )->execute([$qty, $id]);
            }
        }
        $db->commit();
        jsonOk(['ok' => true, 'moved' => count($items)]);
    } catch (Throwable $e) {
        $db->rollBack();
        jsonError('Error al descontar stock: ' . $e->getMessage(), 500);
    }
}

// ─── METAS POR TRABAJADOR ─────────────────────────────────────────────────────
// GET  ?action=worker_goals&month=YYYY-MM  → devuelve metas del mes
// POST ?action=worker_goals                → guarda metas (Solo SA)
if ($action === 'worker_goals') {
    requirePerm($user, 'commissions');

    // Asegurar tabla
    $db->exec("CREATE TABLE IF NOT EXISTS worker_goals (
        id         INT AUTO_INCREMENT PRIMARY KEY,
        worker_id  INT         NOT NULL,
        month      CHAR(7)     NOT NULL COMMENT 'YYYY-MM',
        goal_svc   DECIMAL(10,2) NOT NULL DEFAULT 0,
        goal_prod  DECIMAL(10,2) NOT NULL DEFAULT 0,
        updated_at DATETIME    NOT NULL DEFAULT NOW() ON UPDATE NOW(),
        UNIQUE KEY uk_worker_month (worker_id, month)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    if ($method === 'GET') {
        $month = $_GET['month'] ?? date('Y-m');
        $st = $db->prepare(
            "SELECT wg.worker_id, u.name, wg.goal_svc, wg.goal_prod
             FROM worker_goals wg
             JOIN users u ON u.id = wg.worker_id
             WHERE wg.month = ?"
        );
        $st->execute([$month]);
        $rows = $st->fetchAll();
        // Devolver como mapa {workerId: {svc, prod}}
        $result = [];
        foreach ($rows as $r) {
            $result[$r['worker_id']] = [
                'name'     => $r['name'],
                'goal_svc' => (float)$r['goal_svc'],
                'goal_prod'=> (float)$r['goal_prod'],
            ];
        }
        jsonOk(['month' => $month, 'goals' => $result]);
    }

    if ($method === 'POST') {
        requireSA($user);
        $b     = body();
        $month = $b['month'] ?? date('Y-m');
        $goals = $b['goals'] ?? []; // [{worker_id, goal_svc, goal_prod}]

        $db->beginTransaction();
        try {
            foreach ($goals as $g) {
                $wid  = (int)($g['worker_id'] ?? 0);
                $gsvc = (float)($g['goal_svc']  ?? 0);
                $gprd = (float)($g['goal_prod'] ?? 0);
                if ($wid <= 0) continue;
                $db->prepare(
                    "INSERT INTO worker_goals (worker_id, month, goal_svc, goal_prod)
                     VALUES (?, ?, ?, ?)
                     ON DUPLICATE KEY UPDATE goal_svc=VALUES(goal_svc), goal_prod=VALUES(goal_prod)"
                )->execute([$wid, $month, $gsvc, $gprd]);
            }
            $db->commit();
            jsonOk(['ok' => true, 'month' => $month, 'saved' => count($goals)]);
        } catch (Throwable $e) {
            $db->rollBack();
            jsonError('Error al guardar metas: ' . $e->getMessage(), 500);
        }
    }
}

// ─── GASTOS OPERATIVOS / REPOSICIÓN ───────────────────────────────────────────
if ($action === 'expenses') {
    requirePerm($user, 'admin');
    $db->exec("CREATE TABLE IF NOT EXISTS expenses (
        id INT AUTO_INCREMENT PRIMARY KEY,
        type VARCHAR(40) NOT NULL,
        concept VARCHAR(255) NOT NULL,
        amount DECIMAL(12,2) NOT NULL DEFAULT 0,
        supplier VARCHAR(255) NULL,
        date DATE NOT NULL,
        time VARCHAR(20) NULL,
        registered_by VARCHAR(120) NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_expenses_type_date (type, date)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    if ($method === 'GET') {
        $type = strParam('type', '');
        if ($type) {
            $st = $db->prepare("SELECT * FROM expenses WHERE type=? ORDER BY date DESC, id DESC LIMIT 1000");
            $st->execute([$type]);
        } else {
            $st = $db->query("SELECT * FROM expenses ORDER BY date DESC, id DESC LIMIT 1000");
        }
        jsonOk(['data' => $st->fetchAll()]);
    }

    if ($method === 'POST') {
        $b = body();
        $type = trim($b['type'] ?? '');
        $concept = trim($b['concept'] ?? $b['concepto'] ?? '');
        $amount = (float)($b['amount'] ?? $b['monto'] ?? 0);
        if (!$type || !$concept || $amount <= 0) jsonError('Tipo, concepto y monto son obligatorios');
        $date = $b['date'] ?? $b['fecha'] ?? date('Y-m-d');
        $time = $b['time'] ?? $b['hora'] ?? date('H:i');
        $supplier = trim($b['supplier'] ?? $b['proveedor'] ?? '');
        $registeredBy = trim($b['registered_by'] ?? $b['registradoPor'] ?? ($user['name'] ?? ''));
        $db->prepare("INSERT INTO expenses (type,concept,amount,supplier,date,time,registered_by) VALUES (?,?,?,?,?,?,?)")
           ->execute([$type, $concept, $amount, $supplier, $date, $time, $registeredBy]);
        $id = (int)$db->lastInsertId();
        $st = $db->prepare("SELECT * FROM expenses WHERE id=?");
        $st->execute([$id]);
        jsonOk(['ok' => true, 'row' => $st->fetch()]);
    }

    if ($method === 'DELETE') {
        $id = intParam('id');
        if (!$id) jsonError('ID requerido');
        $db->prepare("DELETE FROM expenses WHERE id=?")->execute([$id]);
        jsonOk(['ok' => true, 'deleted' => $id]);
    }
}

// ─── HEALTH CHECK ─────────────────────────────────────────────────────────────
if ($action === 'health' && $method === 'GET') {
    jsonOk(['ok' => true, 'version' => '4.0', 'time' => date('c'), 'db' => DB_NAME]);
}

jsonError("Acción no encontrada: $action", 404);
