query(" SELECT c.*, COUNT(m.id) as total_movimientos FROM categorias c LEFT JOIN movimientos m ON m.id_categoria = c.id GROUP BY c.id ORDER BY c.nombre "); echo json_encode(['status' => 'success', 'categorias' => $stmt->fetchAll(PDO::FETCH_ASSOC)]); } elseif ($method === 'POST') { $d = json_decode(file_get_contents('php://input'), true); if (empty($d['nombre']) || empty($d['icono'])) { http_response_code(400); die(json_encode(['status' => 'error', 'message' => 'Nombre e ícono son obligatorios'])); } $stmt = $pdo->prepare("INSERT INTO categorias (nombre, icono, color_hex) VALUES (?, ?, ?)"); $stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050']); echo json_encode(['status' => 'success', 'id' => $pdo->lastInsertId()]); } elseif ($method === 'PUT') { $d = json_decode(file_get_contents('php://input'), true); if (empty($d['id']) || empty($d['nombre']) || empty($d['icono'])) { http_response_code(400); die(json_encode(['status' => 'error', 'message' => 'id, nombre e ícono son obligatorios'])); } $stmt = $pdo->prepare("UPDATE categorias SET nombre=?, icono=?, color_hex=? WHERE id=?"); $stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050', (int)$d['id']]); echo json_encode(['status' => 'success']); } elseif ($method === 'DELETE') { $d = json_decode(file_get_contents('php://input'), true); if (empty($d['id'])) { http_response_code(400); die(json_encode(['status' => 'error', 'message' => 'Falta el ID'])); } $count = $pdo->prepare("SELECT COUNT(*) FROM movimientos WHERE id_categoria = ?"); $count->execute([(int)$d['id']]); if ($count->fetchColumn() > 0) { http_response_code(409); die(json_encode(['status' => 'error', 'message' => 'No se puede eliminar: tiene movimientos asociados'])); } $pdo->prepare("DELETE FROM categorias WHERE id = ?")->execute([(int)$d['id']]); echo json_encode(['status' => 'success']); } else { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'Método no permitido']); }